Static Application Security Testing (SAST) is supposed to catch vulnerabilities before they ship.
In practice, many teams end up ignoring it.

TL;DR

  • Most SAST tools produce too many false positives because static analysis lacks runtime context.
  • The precision vs recall tradeoff is real. The more you scan, the more noise you get.
  • What works is dataflow + taint tracking + framework modeling + reachability + triage + suppressions.
  • The goal is not "find everything." The key is to be right often enough that developers trust it.

Here's the problem:

SAST sees code in isolation. Real-world exploitability depends on runtime context the scanner can't reliably model.

Exploitability depends on context SAST cannot always see:

  • Authentication middleware
  • Input validation layers
  • Framework-level protections
  • Type checking
  • Feature flags that disable entire code paths

SAST just sees "there's a path from A to B" and panics. It doesn't know that path is blocked six different ways.

Static analysis has to choose: miss bugs or cry wolf

Every SAST engine faces a tradeoff between precision and recall.

You can tune for fewer false positives (high precision), but then you miss real bugs. Or you can catch everything (high recall), but drown in noise.

This is why some tools let you pick different modes — "daily dev workflow" vs "security audit sweep". It's the same tradeoff, just exposed as a feature.

Infeasible paths: code that exists but never runs

A huge source of false positives is when SAST finds a dangerous path that literally cannot execute.

if user.role != "admin":
    return

## strict type constraint
user_id = int(user_input)
db.execute("DELETE FROM users WHERE id=%s", (user_id,))

A naive scanner might still flag SQL injection if it only pattern-matches on db.execute(...) and doesn’t detect parameter binding + type constraints.

Do note: SAST can’t fully model runtime context, so false positives are inevitable.

The admin check matters for exposure, but it’s not the main reason it’s safe.

Tools don't know your application's context

Even when a finding is technically correct, it might not matter in your codebase:

  • Your auth layer might guarantee that input is safe
  • Your framework might sanitize certain inputs automatically
  • Your business logic might enforce constraints that aren't obvious from static analysis alone

Without app-specific customization, tools over-report by default.

Pattern matching is fast, cheap, and extremely noisy

A lot of older SAST tools still rely heavily on simple pattern matching:

  • Regex searches
  • AST pattern rules
  • "Dangerous API" blacklists (eval, exec, os.system)
  • String-matching heuristics

This catches obvious mistakes, but it also fires on safe code constantly.

Logic bugs are the real threat (and nearly impossible to detect statically)

Broken access control is first in the current OWASP Top 10 (A01:2025), and bugs like these are hard to find with pattern rules:

  • Broken access control (IDOR)
  • Missing authorization checks
  • Privilege escalation in multi-step workflows
  • Race conditions

These require understanding intent, not just code patterns. Even fancy AI-based SAST tools struggle here, often flagging issues based on assumptions rather than proof.

Scale makes everything worse

Even a small false positive rate becomes unbearable at scale.

Let's say you have:

  • 200 repos
  • 10,000 alerts per week
  • 5% false positive rate

That's 500 bogus alerts every week. At that point, it's just spam.

What actually works

Use modern dataflow analysis

Skip pure pattern matching. Look for tools that do:

  • Real taint tracking and dataflow analysis
  • Framework-aware modeling (knows how Django/Rails/etc work)
  • Sanitizer detection
  • Reachability analysis (can this code even run?)

Run different modes for different contexts

High-precision mode for developers. Deep scans for security reviews.

Add a human triage layer

Here's the key insight: don't show developers everything.

Run comprehensive scans in CI, but filter results before surfacing them. Let your security team or a triage process decide what's actually worth showing to devs.

Make suppressions first-class

False positives will never hit zero. Accept that.

Good tools let you suppress findings with context:

  • "Accepted risk"
  • "Not applicable in our architecture"
  • "False positive"

And they make those suppressions reviewable and auditable.

How this looks in a Python scanner

The controls above map to concrete settings. In Skylos, for example:

  • Only new findings. skylos baseline . records today's findings; skylos . --danger --baseline then reports only findings that are not in the baseline.
  • Only the lines you changed. skylos . --danger --diff-base origin/main --diff origin/main reports findings on changed lines. In CI, check out full history (fetch-depth: 0).
  • Severity and confidence floors. --severity high shows only CRITICAL and HIGH findings. --confidence 80 raises the confidence threshold for dead-code results (the default is 60).
  • Suppressions with a reason. # skylos: ignore[SKY-D211] parameters are bound by the driver on the flagged line suppresses one rule on one line and keeps the reason next to the code.
  • Pattern versus flow. Skylos reports every subprocess call with shell=True as SKY-D209 (HIGH), including a hard-coded command such as subprocess.call("ls -la", shell=True). It adds SKY-D212 (command injection, CRITICAL) only when it traces request input into the call. Read the rule ID before deciding how urgent a finding is.
  • Verification passes. A grep-based verification pass runs by default to cut false positives (--no-grep-verify turns it off). --verify-security asks an AI model to re-check high-severity security findings and label the evidence; it needs an LLM API key, sends the flagged code to that provider, and never changes which findings block.

The real problem

False positives aren't just annoying. They actively make your codebase less secure.

Once developers stop trusting the security tool:

  • They ignore all alerts (including real ones)
  • They route around it
  • Actual vulnerabilities slip through

The goal isn't to "scan harder" or "find more issues". It's to be right more often so people actually pay attention when you flag something.