Every team that has rolled out a static analyzer knows the failure mode: the tool fires hundreds of findings, a few are real, most aren't, and within a sprint everyone has learned to bulk-dismiss the lot. The scanner is technically still running. It's protecting nothing.
The false-positive rate — the share of findings that aren't real problems — is the metric that governs whether a scanner gets used or quietly abandoned. But the naive fix (suppress more aggressively) trades false positives for false negatives: hide the noise and you hide real bugs with it. The actual engineering problem is lowering noise without lowering coverage. Here's how.
1. Path-aware severity
The same pattern means different things in different places. A hardcoded credential in `src/` is an error; the identical string in `fixtures/` or `*.test.ts` is almost always a test value. A `Math.random()` in production code is a concern; in a test it's usually fine.
Path-aware severity downgrades findings in test, fixture, story, and example paths rather than firing them at full severity. The finding still appears — you haven't blinded the tool — but it doesn't block the build or scream for attention where it almost certainly doesn't matter.
2. Library-aware safe harbours
A huge fraction of false positives come from not recognising that the code already did the safe thing. A money value in a float is a bug — unless the file imports decimal.js or big.js, in which case the arithmetic is exact. A raw SQL string is injection-shaped — unless it's a parameterised query through a known ORM. An unbounded retry loop is a problem — unless it's wrapped by p-retry.
// Flagged: money in a float const total = parseFloat(req.body.amount); // money-float: error // Safe-harboured: the file uses a decimal library import Decimal from "decimal.js"; const total = new Decimal(req.body.amount); // no finding
3. Explicit, auditable suppression
Sometimes the tool is right that a pattern is present and the developer is right that it's fine. The answer isn't to weaken the rule globally; it's a local, explicit, reviewable suppression — a per-rule marker on the line, such as `// taint-ok` or `// pii-ok` in GateTest, which shows up in code review so a teammate can see the override and the reason.
This keeps the decision where it belongs (with the author, in the diff) instead of in a global config file nobody reads, and it leaves an audit trail. A repo-level `.gatetestignore` exists for the cases that genuinely are repo-wide (a vendored directory, a rule that doesn't apply to your stack) — suppressed findings stay visible in a separate list rather than vanishing.
4. Confidence scoring and gating only on high confidence
Not every true finding is equally certain. A detector can often tell the difference between 'this is definitely a leaked AWS key' (high confidence) and 'this string has the entropy of a secret but might be a hash' (lower confidence). If you gate — block the build — only on high-confidence findings and let the rest surface as warnings, you get the enforcement benefit without the noise cost.
5. Learn from dismissals
The strongest signal about whether a rule is noisy is how often humans dismiss it. If a particular rule is suppressed by customers 90% of the time, that's data: the rule is probably miscalibrated. A feedback loop that watches dismissal rates and recommends severity downgrades turns the crowd's judgement into calibration, so the scanner gets quieter over time exactly where it's wrong — and stays loud where it's right.
The principle underneath
Every one of these techniques shares a goal: be quiet where you're probably wrong, loud where you're probably right, and never silently drop a finding — downgrade it visibly instead. That's how you earn the one thing a scanner can't function without, which is the developer's trust that a finding is worth looking at.