At a glance
- Topic
- The role and limits of static application security testing (SAST)
- Claim
- SAST is a floor to clear, not a report to ship
- Good at
- Known, local, pattern-shaped bugs across a whole codebase, fast and repeatable
- Blind to
- Authorization, business logic, design flaws, and anything needing intent
- The tax
- False positives that erode trust and false negatives that create it falsely
- Standards
- OWASP Code Review Guide, OWASP ASVS, CWE, NIST SAMATE
Why “the floor” is the right metaphor
A static analysis run is the minimum bar, the floor you have to clear before a human should spend an hour on your code. It is cheap, fast, and repeatable, and it catches the embarrassing, mechanical mistakes so that expensive human attention is not wasted on them. That is real value. The mistake is treating the scanner's output as the review, shipping a triaged SAST report to the client and calling it a code audit.
The two are not the same activity. SAST answers “does this code contain any of the patterns I know about?” A code review answers “can this code be made to do something it should not?” The second question subsumes the first and then keeps going, into logic, authorization, and design, where the patterns run out.
This is not anti-tooling. We run SAST on every engagement, and modern engines are genuinely good. The argument is about where the tool's job ends and a human's begins, and about vendors who blur that line to sell a scan as an audit.
What SAST is genuinely good at
Static analysis excels at bugs that are known, local, and pattern-shaped. If a class of flaw looks roughly the same every time it appears and can be recognised from the code itself, a scanner will find it faster and more consistently than any human, and it will find it in every file at once:
- Injection sinks with tainted input, SQL, command, and path injection where data flows from a source to a dangerous sink without sanitisation.
- Dangerous API usage, deprecated crypto, weak hashing, insecure deserialisation calls, disabled TLS verification.
- Hardcoded secrets and obvious misconfiguration, keys in source, debug flags left on, permissive CORS.
- Memory-safety patterns in languages that still have them, and broad linting for the mistakes that correlate with bugs.
Across a large codebase, that coverage is something no human can match. The scanner reads every line every time; a reviewer reads the interesting ten percent once.
How it works, and where the model breaks
Most serious SAST engines are built on data-flow and taint analysis: they model untrusted input as it moves through the program and flag a finding when tainted data reaches a sensitive sink without passing through something the engine recognises as a sanitiser. Interprocedural taint tracking (the kind in tools like CodeQL and Semgrep's paid tiers) follows that flow across functions and files, which is where the technology has genuinely advanced.
# what a taint engine sees, and what it doesn't:
source: req.query.id # tainted input, engine follows it
-> buildQuery(id) # crosses a function, still tracked
-> db.raw(sql) FLAGGED: SQL injection # correct, useful
source: req.user.id # also tainted, but...
-> getOrder(orderId) # the engine sees a clean flow
-> returns another user's order SILENT # no rule says this is wrong
The second case is the crux. Nothing in the data flow is “dangerous” in the injection sense; the query is parameterised, the input is a normal integer. The bug is that the code never checked whether this user may see that order. There is no taint, no sink, and therefore no finding. The scanner is working perfectly and is completely blind.
What it structurally cannot find
The limits are not bugs in the tools; they are consequences of what a static pattern can express. A scanner cannot find a flaw whose definition requires knowing what the application is supposed to do:
- Broken access control and IDOR. Whether a user should be allowed to touch an object is a fact about your business, not your syntax. This is the web's top risk and the one tools are worst at; we cover it in IDOR: the flaw your scanner keeps missing.
- Business-logic flaws. Code that works exactly as written but lets someone apply a discount twice, or withdraw before a balance check, is invisible to a tool that has no model of intent. We give real examples in Business logic bugs.
- Design and architecture weaknesses. A missing trust boundary, a token with the wrong lifetime, a flawed multi-step workflow, none of these live in a single line.
- Chained findings. Two “low” issues that combine into a critical one require a mind that holds both at once and asks “what if I use these together?”
The most damaging finding on a typical engagement is an authorization or logic flaw, exactly the category SAST is structurally unable to reach. If your assurance stops at the scanner, your highest-impact bugs are the ones you never looked for.
The two kinds of wrong
Static analysis is wrong in two directions, and both are costly:
- False positives flag code that is not actually exploitable, a “tainted” flow that is sanitised in a way the engine did not recognise, a sink that is unreachable. A noisy tool trains developers to ignore it, which quietly disables the control you paid for.
- False negatives are worse, because they are invisible. A clean SAST report does not mean the code is secure; it means the code contains none of the patterns this engine knows. The absence of findings is routinely mistaken for the presence of security.
Managing both is itself skilled work. Someone has to confirm the real ones, dismiss the false ones with a reason, and, crucially, understand what the tool never even attempted.
The manual layer
Manual review is where a reviewer reads the code the way an attacker would, carrying context a tool cannot: what the application is for, who the users are, and what “abuse” would mean here. In practice that means:
- Mapping trust boundaries, every point where data or control crosses from less-trusted to more-trusted, and checking each for a real authorization decision.
- Following the interesting flows by hand, authentication, session handling, payment, privilege changes, the paths where a logic error is catastrophic.
- Reasoning about state, race conditions, multi-step workflows, and assumptions that hold in isolation but not under concurrency.
- Triaging the SAST output, confirming true positives, discarding false ones, and treating the scan as a lead list, not a verdict.
The right division of labour: let the tool read every line and surface the mechanical, pattern-shaped bugs, then spend human attention on the logic, authorization, and design it cannot see. Neither replaces the other; using one without the other is the actual mistake.
Using both well
| Question | SAST | Manual review |
|---|---|---|
| Does a known bad pattern appear? | Excellent | Slow, redundant |
| Coverage across a huge codebase | Complete, every line | Selective, by risk |
| Is this user allowed to do this? | Blind | Core strength |
| Does the logic match intent? | Blind | Core strength |
| Can two lows chain to a critical? | No | Yes |
| Cost per run | Near zero | Human time |
Read the table as a workflow, not a competition. SAST first, in the pipeline, on every commit, to hold the floor. Manual review periodically, focused by risk, to reach the ceiling. The output of the first should feed the second, not stand in for it.
What good looks like
- Run SAST continuously, in CI, tuned to keep false positives low enough that developers still trust it.
- Never ship a scan as an audit. A code review deliverable should contain findings the tool could not have produced, or it was not a review.
- Treat a clean report with suspicion. Ask what was not tested: authorization, logic, design. That list is where your risk actually is.
- Verify by hand. Every finding we report, from a tool or not, is confirmed exploitable and reproduced, never a raw scanner dump.
Key takeaway
SAST is the floor: fast, complete over the whole codebase, and excellent at the known, local, pattern-shaped bugs. It is structurally blind to authorization, business logic, and design, which is exactly where the worst findings live.
So clear the floor with the tool, then send a human after the ceiling. A code review that stops where the scanner stops has skipped the part that was worth paying for.
References & further reading
- OWASP, Code Review Guide, the reference for what a manual secure code review covers.
- OWASP, Application Security Verification Standard (ASVS), the requirements a review verifies against.
- MITRE, Common Weakness Enumeration (CWE), the taxonomy SAST rules map to.
- NIST, SAMATE project, research on the capabilities and limits of static analysis tools.
- OverWatch Labs, IDOR: the flaw your scanner keeps missing and Business logic bugs, the two categories SAST cannot reach.