The bullshit of slow SAST scans

The bullshit of slow SAST scans

You change a small piece of code, push the branch and wait. The tests finish. The build finishes. The security job is still running. Half an hour later, you get a finding that nobody has explained how to handle. Apparently, this is what secure delivery looks like.

I have no objection to static application security testing. I object to treating waiting time as proof that we take security seriously. A slow SAST pipeline deserves the same engineering attention as a slow application.

What are we waiting for?

SAST analyses source code, or a representation of it, for patterns that may indicate vulnerabilities. Depending on the tool and language, this can involve substantial preparation and analysis. That work can be valuable. It is still worth knowing which part of the pipeline is consuming the time.

A job called “security scan” may spend most of its life waiting for a runner, installing dependencies or building code. If we treat its total duration as an indivisible fact, we cannot improve it intelligently.

Measure the pieces

Take a fictional 30-minute job. It waits eight minutes for capacity, spends seven preparing the environment, analyses for twelve and uses three publishing results. Those numbers are an illustration, not a benchmark. They show why “our analyser takes thirty minutes” can be the wrong diagnosis.

Record queue time and execution time separately. Look at typical runs and the slowest ordinary runs. Check whether the same commit triggers duplicate workflows. Inspect whether a job repeats setup that has already happened elsewhere.

Then improve the largest avoidable cost. Adding more query exclusions will not fix a queue. Buying a larger runner will not fix two workflows doing the same work for every push.

Fast feedback needs a design

I want a developer to discover an obvious problem while the change is still in their head. If a compilation error appears in two minutes, there is no reason to hide it behind a twenty-minute job that had to run first.

Independent checks can run in parallel when capacity allows. Language-specific analysis may be separable too. GitHub's CodeQL performance guidance describes language matrices, resource constraints and the trade-offs of splitting analysis.

Cache only inputs that can be reused correctly, with keys that reflect relevant dependency and tool versions. A fast result from the wrong inputs is not an optimisation. Keep the analysed commit and the result tied together so that an old green check cannot approve a newer change.

Cancel the right work

For ordinary pull-request feedback, superseded runs may be candidates for cancellation. If a developer pushes a corrected version, spending scarce capacity on several obsolete revisions can delay the result that matters.

Required release evidence or a deliberately scheduled full scan is a different case. Work out which run must finish and which result your merge policy accepts. “Cancel everything older” is no more thoughtful than “run everything twice”.

A diff is not the whole program

“Just scan the changed lines” sounds attractive. It can also miss the relationship that makes a change dangerous. A value may enter through one component, pass through several others and reach a sensitive operation somewhere else.

Incremental analysis can be useful when the tool supports it with guarantees that fit your needs. Restricting analysis manually to a few files is not automatically equivalent. CodeQL's guidance explicitly warns that splitting analysis can prevent it from following data between components.

If you reduce the scope of pull-request checks, retain appropriate complete analysis and decide when its result is required. Running something overnight means the finding may arrive after a merge. That timing is a risk decision, not a free speed improvement.

Name the trade-off

I want a team to say: “This check runs here because it protects this boundary, and this other check runs later because we accept that detection window.” Then someone can challenge the decision.

“Security signed off the pipeline” is too vague. Which risks did we discuss? What changed since then? Who notices if a required scan stops running altogether?

A finding needs somewhere to go

A tool can produce an alert. A team still has to decide whether it is relevant, how urgent it is and who will act. Without that process, we have built an expensive notification generator.

For a new finding I want the affected code, an understandable explanation and a named owner. Severity is useful, but context matters too: what can an attacker reach, what data is involved, and which controls already apply?

An existing backlog should not make every change equally impossible to merge. Establish a reviewed baseline where appropriate, assign remediation work and stop new relevant findings from quietly joining the pile. A baseline records debt; it does not make the debt safe.

Exceptions should expire

Sometimes a finding is a false positive. Sometimes it is real but needs a coordinated fix. Record which of those you believe, with evidence and an owner. If an exception is temporary, give it an expiry and a review point.

A comment saying “accepted risk” without any explanation is not useful to the next developer. They cannot tell whether the reasoning still applies or whether someone simply wanted to get home.

Know what the scanner cannot tell you

SAST is one source of evidence. Dependency vulnerability analysis, secret scanning and tests against a running application ask different questions. They may share a product dashboard, but that does not make them interchangeable.

More importantly, a scanner may have no idea that an invoice belongs to a particular customer. Code that loads an invoice by ID and returns it can look perfectly ordinary. The missing ownership rule lives in the business requirement.

Write an integration test in which one customer requests another customer's invoice. Review where the backend enforces that boundary. I cover that workflow in Security by design in Angular.

Test the pipeline too

Check that required jobs actually start for relevant changes and that a failed analysis does not count as success. Verify the branch protection or equivalent merge rules. An impressive workflow file is not protection if merging does not depend on its result.

Use documented scanner test fixtures or a controlled test repository to verify alert handling. Do not introduce live credentials to see whether secret scanning notices. The test should exercise the process without creating the incident it is meant to prevent.

Waiting has consequences

Long feedback loops encourage larger batches and context switching. If the team expects a long wait, they may stack more changes into one request or move to another problem before the result arrives. That makes the eventual review harder.

I would track feedback time alongside whether important findings are acted on. A faster pipeline that silently excludes the dangerous code is a failure. A thorough pipeline whose findings nobody reads is also a failure.

The goal is a credible result early enough to influence the work. Sometimes that means more runner capacity. Sometimes it means fixing the workflow. Sometimes it means accepting that a particular analysis genuinely needs time and making the rest of the process work around it.

I want security checks that developers understand and trust. Make the useful analysis reliable, make its results actionable, and remove the accidental waiting. Thirty minutes of silence is not a security strategy.

Comments

There are no comments yet, leave yours below.

Leave a comment

Do you have an addition, question or experience related to this article? Share it below.

Comments are briefly reviewed before they appear.

Read more about:

Security by design in Angular

Security by design in Angular

A route guard and a green pipeline do not stop one customer reading another customer's invoice. Using Angular and a .NET API, I turn a security requirement into server-side ownership checks and tests. Security by design starts with boundaries and evidence, before choosing a scanner.

Continue reading

The bullshit of code coverage

A test suite can reach 100% coverage and still charge the wrong shipping fee or take a payment twice. Through boundary cases, misleading mocks and missing requirements, I examine what coverage actually proves. The useful question is not whether the code ran, but whether the tests would catch the mistakes your customers care about.

Continue reading

The bullshit of vibe coding

The bullshit of vibe coding

AI can produce a convincing interface long before the application is safe or maintainable. Behind the polished facade, permissions, failure handling and business rules still need engineering. I examine what vibe coding leaves out, where prototypes help and who owns the code after the demo.

Continue reading