Safeguard
Security Guides•

Why most vulnerability findings never touch your code

Why a dependency scanner flags far more CVEs than can hurt you, and how reachability analysis separates vulnerable code you ship from code you call.

Safeguard Security Team
4 min read

Most findings are noise because scanners match package versions, not behavior. A CVE usually sits in one function inside one package. If your application never calls that function, the vulnerable code never runs for you. Reachability analysis checks that call path, so you fix what can execute first.

How a version match creates a finding

A basic dependency scanner reads your lockfile, lists every package and version, and compares each one to an advisory database. If the version falls inside an affected range, you get a finding. That is a fair first step. It is also all the scanner knows.

The OSV schema shows how coarse this can be. Affected ranges are expressed as events such as introduced and fixed versions. A package version is flagged if it lies inside a range. The schema also lets an ecosystem attach extra detail, such as the affected functions, but it does not define a standard field for it, so many records carry only version data.

Why a package is bigger than the bug

Think of a large library with hundreds of functions. An advisory names a flaw in one parser path. Your code might use the library for something else entirely, like date formatting. The package is present and vulnerable by version. The flawed path is never entered.

Now add transitive dependencies. You import package A, A imports B, B imports C, and C has the advisory. Your code may never reach C's flawed function, and A may never call it either. Each hop is another chance for the path to be dead.

What reachability analysis adds

Reachability analysis builds a map of which functions call which other functions, then asks whether a path exists from code that runs in your application to the vulnerable function. If a path exists, the finding is reachable and moves up. If none exists, it moves down the queue.

The Go team describes the idea in plain terms: its vulnerability checker "only surfaces vulnerabilities that actually affect you, based on which functions in your code are transitively calling vulnerable functions." The same logic applies in other ecosystems, though precision differs by language.

What you should not conclude

Not reachable does not mean harmless forever. It means no call path was found by the analysis you ran, at the code state you scanned. Dynamic features can hide calls, and code changes tomorrow. Read more in what reachability analysis cannot tell you.

It also does not replace severity. A reachable low-severity bug can matter less than an unreachable critical one that becomes reachable next sprint.

A practical way to use it

  1. Keep the full finding list. Do not delete unreachable items.
  2. Sort reachable findings first and fix those on your normal schedule.
  3. Mark unreachable ones with a reason and a review date.
  4. Re-run the analysis when dependencies or entry points change.

Click through it at your own pace, or open it full screen.

Safeguard includes reachability analysis alongside SBOM and dependency scanning, and you can try a free scan on a public repository.

Quick check

Is an unreachable finding a false positive?

Not exactly. The package version is truly affected. What differs is whether your code can execute the flawed function. Treat it as lower priority with a reason recorded, not as an error.

Does reachability analysis need running code?

Not for the static kind. It reads source or compiled code and builds a call graph. Dynamic approaches observe running code and have different strengths.

Sources

Last reviewed: 19 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.