Safeguard
AppSec

Recall Is Easy. Your SAST Tool's Real Metric Is the Mute Rate

Any scanner can find every vulnerability by flagging everything. The number that decides whether a tool survives contact with a development team is how often it is wrong.

Yukti Singhal
Application Security Engineer
5 min read

Every static analysis tool can achieve 100% recall. The implementation is one line: report every line of every file as vulnerable. Nothing is missed.

The reason nobody ships that tool is the same reason recall is the wrong headline metric. What determines whether a scanner is used or bypassed is not how much it finds — it is how often the thing it found turns out to be nothing.

The arithmetic of a build gate

Put a scanner in CI with a policy that blocks on High findings. Suppose it produces 40 High findings per week across a mid-sized estate, and 30% of them are false.

That is twelve wrong blocks a week. Twelve times a developer stops what they are doing, reads a finding, works out that the sink is unreachable or the input is a compile-time constant, and files a suppression. Each one costs maybe twenty minutes including the context switch.

Four hours a week of engineering time spent proving a tool wrong. That budget gets cut, and it is never cut by improving the tool. It is cut by one of:

  • moving the gate from blocking to advisory, after which nobody reads it
  • adding a blanket suppression for the noisiest rule, which also suppresses its true positives
  • excluding the noisiest directory, which is usually the largest and most important one

All three are rational responses. All three are indistinguishable, six months later, from not having a scanner.

Why false positives are structurally expensive

The asymmetry is not just about time. It is about who pays and what they learn.

A false negative costs the security team, later, quietly. It is invisible until an incident, and by then the tool is not the thing being examined.

A false positive costs a developer, now, visibly. They experience the tool as an obstacle, and the lesson they take is calibrated to how often it happened — not to how severe the misses were.

Trust in an automated system is a function of its error rate as experienced by the person it interrupts. Once a scanner has been wrong four or five times in a row for one team, that team's default response to the next finding is "probably another one of those". At that point the tool's true positives cost the same as its false positives, because both are ignored.

Measuring it without fooling yourself

The trap is measuring against the wrong corpus.

Do not measure on your own fixtures. They were written by the same people who wrote the rules, and they encode the same assumptions. A rule passes its own fixture by construction.

Do measure on a labelled public suite. The OWASP Benchmark gives you a false positive rate against ground truth you did not author. Synthetic, but adversarial and honest.

Then measure on real repositories, by sampling. Take a stratified sample of findings from actual scans — 20 per rule, per language — and have someone adjudicate each one. It is slow, and it is the only way to learn how your rules behave on code that was not written to test them.

And track the mute rate in production. Every suppression, dismissal and "won't fix" is a user telling you a finding was not worth acting on. Aggregate that by rule and you have a continuously-updated false positive signal generated by the people who know the code. A rule with a 70% dismissal rate is not a rule; it is a notification.

The fixes, roughly in order of payoff

Reachability. A vulnerable function nothing calls is not an emergency. Building a call graph and checking whether the sink sits on a path from an application entrypoint removes a large fraction of dependency findings without weakening a single rule.

Environmental preconditions. Many advisories require conditions beyond the vulnerable version — a specific platform, an enabled feature, a particular configuration. Checking those turns "affected version present" into a real verdict.

Context-aware sanitiser recognition. Most XSS false positives are escaped output the engine did not recognise as escaped. Knowing the framework's escaping helpers by name is unglamorous and pays immediately.

Confidence on the finding, not just severity. Severity says how bad it would be. Confidence says how sure you are. Collapsing them into one number forces the engine to either inflate uncertain findings or hide certain ones.

Say what you did not check

The counterintuitive part: reducing false positives is not the same as reducing findings, and a tool that quietly drops what it cannot decide is worse than one that says so.

If reachability analysis could not run — no call graph, an unsupported language, a build that did not resolve — the finding should come back marked unknown, not silently downgraded. A verdict of "not exploitable" that actually means "we could not tell" is a false negative wearing a false positive's uniform, and it is the failure mode that survives longest because nobody complains about it.

The tool that gets adopted is the one where every finding a developer opens turns out to be worth the click, and where the ones it is unsure about say so out loud. That is a much harder product than the one that finds everything.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.