Safeguard
Application Security•

How do you cut false positives from reachability analysis?

Reachable does not mean exploitable. Combine static, runtime and configuration signals, keep unknown visible, and add exploitation data to cut the noise.

Safeguard Security Team
5 min read

To cut false positives from reachability analysis, add signals rather than trusting one. Pair static call graphs with runtime data and configuration checks, keep "unknown" results visible instead of hiding them, and rank what remains by exploitation evidence. Reachable means a call path exists, not that an attacker can use it.

This is a guide for people who already run a scanner with reachability and still drown in alerts. It uses Safeguard's reachability docs as a worked example and avoids vendor comparisons.

What does a false positive mean here?

Two different things get the same label:

  • A finding flagged reachable where no attacker can actually trigger the flaw, for instance because the route is disabled in production.
  • A finding flagged vulnerable in a dependency whose vulnerable function your code never calls, which reachability should have filtered out.

The second is what reachability is for. The first is what you fix by adding context.

What are the three signals?

Safeguard's docs describe three kinds of reachability:

  • Static: builds a call graph from source, lockfiles and bytecode to check for a path to the vulnerable code.
  • Dynamic: a runtime collector records which modules and functions load and run in production, and flags vulnerable paths that never execute as cold.
  • Configuration: parses framework configuration, such as Spring bean definitions or Next.js route files, to see whether code is wired in.

Each fails differently. Static analysis over-approximates, so it flags paths that can exist but never run. Dynamic analysis only sees what ran during the observation window, so a rare job can look cold. Configuration analysis cannot enumerate everything. Using all three lets one signal check another.

How are the results classified?

The docs use four classes: Reachable, Conditionally reachable (a path exists but needs a feature flag, route or configuration that is not enabled), Unreachable, and Unknown. Unknown is treated conservatively as potentially reachable.

Do not fight that last rule. The docs say reflection, eval and dynamic dispatch yield unknown results, and that "Safeguard marks these as unknown and does not suppress them." Suppressing them would trade noise for blind spots. Instead, work the unknown pile down by hand over time.

Which steps cut noise most?

  1. Turn on runtime data for services that matter. A function that never loaded in production over a representative period is strong evidence, with the caveat about rare jobs.
  2. Separate production from other environments. A finding in a test fixture should not share a queue with an internet facing service.
  3. Use exploitation data. Rank reachable findings with EPSS and CISA's Known Exploited Vulnerabilities list. EPSS estimates the probability that a published CVE will be exploited in the next 30 days. KEV lists vulnerabilities exploited in the wild. A reachable, KEV-listed finding in production is a different priority from a reachable, low-probability one in an internal tool.
  4. Check conditions. For conditionally reachable findings, record which flag or route would make them live, and alert if that setting changes.
  5. Record suppressions with reasons and review dates. A suppression without a reason becomes a future incident.
  6. Sample. Each month, pick a few findings marked unreachable and confirm by hand that they really are.

What are the limits you should say out loud?

The docs list them. Data-driven vulnerabilities can still be exploited through inputs even when static analysis finds no call path. Paths that depend on configuration the analyzer cannot enumerate may be missed. The docs also describe a 60 to 80 percent noise reduction as an aggregate figure across customers, not a guarantee. Measure your own reduction on your own repositories before you promise anyone a number.

Language support differs too: static and dynamic analysis are generally available for Java, Kotlin, Scala, Python, JavaScript, TypeScript, Go, Rust, C, C++ and C#, while Ruby and PHP static analysis, and Swift and Objective-C analysis, are in preview. Read preview results with extra care.

See the exploitability check demo.

What to do

  1. Baseline: count open findings and how many are reachable, conditional and unknown.
  2. Enable runtime collection on your top services.
  3. Add exploitation data to the ranking.
  4. Create a rule: reachable plus known exploited plus production is first in the queue.
  5. Review unknown results on a schedule, and record the outcome.
  6. Sample unreachable findings monthly.
  7. Re-measure after a quarter and report the change.

Quick check

Is an unreachable finding safe to ignore?

Mostly, but not always. Input driven flaws can be exploited without a visible call path, so sample unreachable findings and keep the reasons.

Why not suppress unknown results?

Because they mark places the analyzer could not decide, such as reflection. Hiding them would hide real risk.

Sources

Last reviewed: 18 October 2026.

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.