Nullify

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

SAST False Positives: Why They Happen and How to Reduce Them

SAST false positives are findings from a static application security testing tool that flag code as vulnerable when it isn't. They happen because most SAST tools match risky-looking patterns without understanding the code around them. You reduce them by teaching the tool your code, checking reachability before a person triages, and recording every decision.

The tool misses what a reviewer sees in seconds: a custom sanitizer, a framework protection, a function nobody calls, an endpoint that only runs behind the VPN. Below are the seven causes behind most of that noise, how to measure your rate honestly, and eight fixes in the order we'd apply them.

What is a SAST false positive?

A SAST false positive is a finding that reports a vulnerability that doesn't exist. The flagged code can't be exploited the way the finding describes, usually because the input is already validated, the data never reaches the dangerous function, or the "dangerous" function isn't dangerous in that context. For the general term across all security tools, see the false positive glossary entry.

Teams often lump three different outcomes together. Only one of them is a false positive.

OutcomeWhat it meansWhat to do
False positiveThe vulnerability doesn't exist.Dismiss, record why, and fix the rule or config that produced it.
True but not reachableThe flaw is real, but untrusted input can't reach it today (dead code, test fixture, disabled feature).Deprioritize, keep it on record, re-check when the code changes.
True but low priorityThe flaw is real and reachable, but the impact is small.Rank it below real risk and fix it on a normal timeline.

Calling all three "false positives" hides real flaws and makes your rate meaningless. A false positive is a wrong answer. An unreachable finding is a right answer you can schedule.

What false positives cost a security program

False positives cost more than triage time. They teach developers that security findings are usually wrong.

Across five Nullify production tenants, 57% of 14,847 SAST findings triaged over 90 days were ultimately classified as false positives. In an earlier limited deployment to ten repositories, triage turned up 547 false positives against 105 true positives. Five of every six findings in that deployment were noise.

At those ratios, volume hides the real findings. Someone has to read every finding to find the few that matter, and developers who've been burned before stop reading closely. The cost lands twice: in security team hours, and in the real vulnerability nobody acted on.

Why SAST false positives happen

Static analysis reads code without running it. That forces the tool to guess how data flows, which functions are called and what the code is for. Most false positives come from one of these guesses going wrong.

1. Pattern matching without semantics

Many rules flag syntax that looks dangerous: string concatenation near a SQL call, a variable passed to exec, a URL built from a parameter. The rule fires whether or not the value is attacker-controlled. A query built from a hardcoded table name and an integer ID matches the same pattern as a real injection.

2. Custom sanitizers and validators the tool doesn't know

Most codebases have their own validation helpers. If the tool doesn't recognize them, every path through them looks unsanitized.

```python from app.security import safe_identifier # allow-list of known column names

def sort_orders(request): column = safe_identifier(request.args["sort"]) # raises on anything unexpected return db.execute(f"SELECT * FROM orders ORDER BY {column}") ```

A pattern-based rule sees request input in an f-string passed to execute and reports SQL injection. A reviewer sees an allow-list and dismisses it. Until the tool learns that safe_identifier is a sanitizer, it reports this on every scan, in every file that uses it.

3. Framework behavior the tool can't see

Frameworks handle a lot of security implicitly: ORMs parameterize queries, template engines escape output, middleware enforces authentication, decorators check permissions. A tool that doesn't model the framework reports the raw operation and misses the protection.

4. Over-approximated data flow

To avoid missing anything, static analysis assumes every branch can run and every call through an interface might reach any implementation. That's the safe choice, and it produces paths that can't happen at runtime. Go's own govulncheck documentation notes that it analyzes function pointer and interface calls conservatively, "which may result in false positives." Any static analyzer that tries not to miss bugs makes the same trade.

5. Test, fixture, generated and vendored code

Test fixtures contain hardcoded credentials and deliberately unsafe inputs. Generated clients and vendored libraries contain patterns you'll never edit. Scanning them with production rules produces findings nobody can or should fix.

6. Dead code and disabled features

Code that's never called, or that sits behind a feature flag that's off, can contain real flaws that aren't exploitable today. These are "true but not reachable," but a tool that can't tell the difference reports them at full severity.

7. Missing deployment and business context

The code can't tell the tool whether an endpoint is internet-facing, admin-only or behind a VPN. Without that context, an internal maintenance script and a public checkout API get the same severity for the same pattern.

How to measure your SAST false positive rate

You can't reduce a rate you don't measure. Track triage outcomes for a fixed period, then calculate:

False positive rate (of findings triaged) = findings dismissed as false positive ÷ all findings triaged

Four rules keep the number honest:

  • Count only triaged findings. Untriaged findings have no outcome yet.
  • Keep the three outcomes separate. "True but not reachable" isn't a false positive.
  • Record the cause. Tag each dismissal with why (unknown sanitizer, test code, framework protection). The cause tells you which fix removes the most noise.
  • Measure per rule. Noise is rarely spread evenly, so a short list of rules often accounts for much of it.

To compare tools before buying, run them against a test suite with known answers. The OWASP Benchmark is an open-source Java and Python application built for this: its vulnerabilities are meant to be actually exploitable, and its scorecards show each tool's true positive and false positive rates side by side.

How to reduce SAST false positives

Work through these roughly in order. The first four are configuration you can do with any tool.

1. Exclude code you'll never fix

Exclude test directories, fixtures, generated code and vendored dependencies from production rules. Scan dependencies with SCA instead. This is often the quickest cut in noise, and it needs no rule changes.

2. Teach the tool your sanitizers and frameworks

Register your validation helpers as sanitizers, and make sure framework support is turned on for the frameworks you use. Every recognized sanitizer removes a whole class of repeat findings across the codebase.

3. Tune or retire your noisiest rules

Rank rules by false positive rate from your triage data. Fix the worst ones with tighter conditions, move low-value rules to an "audit" profile, and retire rules your team dismisses every time. Don't turn off whole vulnerability classes to make the count drop.

4. Record triage decisions and make them stick

When someone dismisses a finding, record the reason, and make sure the dismissal survives rescans, refactors and file moves. Re-triaging the same false positive every week is the fastest way to lose a developer's attention.

5. Check reachability before anyone triages

Trace whether untrusted input (HTTP requests, API calls, file uploads) can actually reach the flagged code, and whether the code is called at all. Findings in dead code or behind disabled flags drop to the bottom of the queue with the reason attached.

6. Add exploitability and deployment context

Ask whether the affected service is deployed, whether it's exposed to the internet, and what controls sit in front of it. A finding that's reachable in code but runs only in an internal batch job ranks below the same finding in a public API.

7. Scan the pull request and baseline the backlog

Findings on a pull request are reviewed by the person who just wrote the code, while it's fresh. They're faster to confirm or dismiss, and real ones never reach the backlog. When you first roll a tool out on an existing codebase, baseline the findings already there and work them as a separate backlog, so new pull requests only show what the change introduced.

8. Feed outcomes back into the tool

Every dismissal is a signal. Review the dismissal causes monthly and turn the top ones into config changes: a new sanitizer, a tighter rule, an excluded path. The goal is a rate that keeps falling, not a one-time cleanup.

Common mistakes that trade noise for missed bugs

Every fix above has a cheap version that lowers the count without lowering the noise. Each of these raises your false negatives, the real flaws a tool fails to report.

  • Suppressing in bulk. Dismissing every finding from a rule without reading them hides the real ones.
  • Turning down severity to shrink the queue. That changes the label, not the noise.
  • Counting "not reachable" as false. It inflates your false positive rate and loses track of real flaws that become reachable later.
  • Measuring tools by finding count. More findings isn't better coverage. Precision (how many reported findings are real) matters as much as recall (how many real flaws get reported).
  • Leaving triage to developers. If developers are the last line of triage, they learn to ignore the queue.

A checklist for cutting SAST noise

  • Triage outcomes are recorded as false positive, true but not reachable, or true.
  • Each dismissal records its cause.
  • False positive rate is tracked per rule and per repository.
  • Test, fixture, generated and vendored code is excluded from production rules.
  • Custom sanitizers and validators are registered with the tool.
  • Framework support is enabled for every framework in use.
  • The noisiest rules have been tuned or retired.
  • Dismissals persist through rescans and refactors.
  • Reachability and exposure are checked before human triage.
  • Every pull request is scanned before merge, and the existing backlog is baselined.
  • Dismissal causes are reviewed monthly and turned into config changes.

How Nullify handles SAST triage

Nullify's code security scanning is AI-native static analysis that reasons about how your application works instead of matching signatures. It traces untrusted input top-down from HTTP requests, API calls and file uploads, and bottom-up from dangerous sinks back to their sources, with framework-aware analysis for Spring, Django, Flask, FastAPI, Rails, Express, Next.js, Laravel and others across 16 languages.

Before a person sees a finding, it's validated on reachability, exploitability and business context. Findings in unreachable code, test fixtures and benign configurations are filtered out, and findings are re-triaged when the code changes. Your team's overrides persist through that re-triage. When reachability is genuinely unclear, the system records "uncertain." It never upgrades a guess to "not reachable."

Nullify scans every pull request and every default-branch commit. When a finding is real, it opens a minimal, build-validated fix for your team to review. It never auto-merges.

A false positive is a wrong answer. Fix the rule that gave it, and it stops coming back.

Frequently asked questions

What is a good false positive rate for SAST?

There's no universal benchmark, because rates depend on the language, the rules and how you count. For reference, 57% of 14,847 SAST findings in Nullify's production data were ultimately false positives. The useful target is a rate that falls quarter over quarter, measured per rule.

Why does SAST have so many false positives?

Static analysis reads code without running it, so it has to guess how data flows and which code runs. To avoid missing real flaws, most tools guess conservatively, and they can't see your custom sanitizers, framework protections or deployment context. Each wrong guess becomes a finding.

Is a vulnerability in dead code a false positive?

No. If the flaw is real, it's a true finding that isn't reachable today. Rank it low and keep it on record, because a refactor or a new caller can make it reachable. Marking it false loses the history.

Can AI reduce SAST false positives?

Yes, when it adds context the rules lack: whether input is attacker-controlled, whether a helper is a sanitizer, whether the code runs in production. AI triage should show its evidence for each decision, so a reviewer can audit it instead of redoing it.