All posts
·2 min·Alex Werner

Why diff-only scanners miss merge-induced vulnerabilities

Most security scanners review a pull request the way a spell-checker reviews a sentence: line by line, in isolation. That works for the obvious things, a hardcoded key, an eval on user input, a dependency with a known CVE. It falls apart for the vulnerabilities that actually hurt, because the worst ones do not live in a single diff.

The merge that creates a hole

Picture two open pull requests against develop:

  • Branch A adds a handler that forwards a user-supplied URL to an internal HTTP client. On its own, that client enforces an allowlist, so there is no SSRF. Safe.
  • Branch B refactors the same HTTP client and, in the cleanup, quietly drops the allowlist because "nothing untrusted calls this yet." Also safe, in isolation.

Each PR passes review. Each scanner says green. Then both land in develop, and now an attacker- controlled URL flows into a client that no longer checks where it points. You have shipped an SSRF that existed in neither branch. No per-PR tool can see it, because the vulnerability is a property of the combination, not of either change.

Why diff-scoped review is structurally blind

A diff is a tiny, local view. It tells you what changed, not what the change now reaches. To catch a merge-induced flaw you need three things a diff does not give you:

  1. The whole-codebase context - the call graph and trust boundaries, so you know which functions a change can actually reach.
  2. The other in-flight branches - what else is about to land on the same base.
  3. The prospective merged state - the code as it will exist after both merges, not as it exists in either branch today.

How Stateward approaches it

Stateward builds a knowledge base of your whole project, the module and call graph, the trust boundaries, which dependencies are actually reachable, and keeps it warm between runs. When a pull request targets an integration branch, it does not just review that diff. It computes the prospective merged graph across the sibling branches and looks for paths that exist only after the merge: a source in one branch reaching a sink in another, a guard removed here that something relies on there.

When it finds such an interaction, it does not stop at "review this." It runs a focused deep audit over the merged state to confirm whether the interaction is actually exploitable, and reports it with a reproduction and a verdict, before it reaches your default branch.

That is the difference between a scanner and a reviewer. A scanner checks a line. A reviewer understands the system. Catching merge-induced vulnerabilities is one of the clearest places that distinction shows up, and one of the reasons I built Stateward to reason over the whole codebase, not just the diff.

Want this kind of review on your own pull requests?

Get started free