Scanners find patterns, reviewers find intent
Automated code scanning is genuinely useful, and any serious team should run it. Static analysis catches the known dangerous function, the unsanitised input reaching a query, the hardcoded secret, the outdated dependency with a published vulnerability. It never gets bored, it runs on every commit, and it clears out a large volume of the mechanical mistakes that would otherwise reach production. Starting a security conversation by dismissing scanners is a mistake.
But a scanner reasons about patterns, and a large share of serious security flaws are not patterns. They are gaps between what the code does and what the business intended it to do, and that intent is not written anywhere the tool can read. A reviewer who understands the system is looking for a different thing entirely: not code that looks dangerous, but code that is behaving correctly according to its logic and wrongly according to its purpose.
Authorization is the clearest example
Consider an endpoint that returns an invoice by its identifier. The code is clean. Input is validated, the query is parameterised, the user is authenticated, the dependencies are current. A scanner passes it without comment, correctly, because there is nothing pattern wise wrong with it. A reviewer asks the question the scanner cannot: does this code check that the invoice belongs to the user requesting it? If it does not, any logged in user can read anyone's invoices by changing a number, and that is one of the most common serious findings in web applications.
The scanner cannot find this because there is no dangerous pattern to match. The presence of an authorization check, and whether it covers the specific object being accessed, is a question about meaning. Only someone who understands that invoices belong to customers and must not leak between them can see that the check is missing.
Insecure design and dangerous defaults
Some of the most expensive flaws are not bugs at all, they are decisions. A password reset flow that is convenient and also lets an attacker take over an account. A default configuration that is permissive so the product works out of the box, and stays permissive in production because nobody changed it. A trust relationship between two services that made sense when they were one system and is now a lateral movement path. Each of these is the code working as designed, where the design is the problem.
A scanner has no concept of design. It cannot tell you that a feature is a bad idea, that a default is dangerous in your context, or that two components should not trust each other the way they do. Those judgements require holding the whole system in mind and asking what an adversary would do with it, which is a human activity.
A concrete second example makes the gap clear. Imagine an online form that lets a customer apply a promotional code for a discount. Each request is well formed, the code is valid, and the discount logic is correct in isolation. A reviewer who understands the business asks whether the same code can be applied twice in one order, whether it can be stacked with another, and whether a rapid sequence of requests can race the check that marks it as used. None of those questions has a dangerous pattern for a scanner to match, because every individual request is legitimate. The flaw lives in the interaction between requests and the business rule they are bending, which is exactly the territory a tool cannot reason about and a person can.
- Multi step flaws, where each step is safe alone but the sequence enables something none of them intended.
- Trust assumptions that were true in an earlier architecture and silently became false.
- Race conditions in business logic, such as spending the same balance twice before the first transaction settles.
- Data that is technically access controlled but exposed through an export, a log, or an error message.
The two are not rivals
The productive arrangement is not scanner versus human, it is scanner then human. Let automation clear the mechanical layer on every commit, so the injection, the stale dependency, and the leaked secret are caught cheaply and continuously. That frees a reviewer to spend their attention where it is irreplaceable: on authorization, on design, on the logic that only makes sense once you understand what the application is for.
A review that just re runs the scanner and reads its output is not adding the thing that matters. The value a human brings is the model of the system in their head and the adversary's question they keep asking against it. That is why serious secure code review reads the authorization logic, walks the sensitive flows, and interrogates the design, rather than grading the tool's report.
What this means for how you buy security
If a security assessment consists entirely of a scanner's output reformatted into a document, you have bought the cheap half and been charged for the whole. The mechanical findings matter and should be there, but the findings that keep a business owner up at night, the ones where a customer can reach another customer's data or a workflow can be bent to give something away, come from someone who understood the system well enough to ask.
The practical test when engaging a reviewer is simple: ask how they will reason about authorization and business logic, not just which tools they run. The answer tells you whether you are buying pattern matching or judgement. Both have their place, but only one of them finds the flaw that a tool, by its nature, was never going to see.
