Safeguard
Industry Analysis

Reachability Analysis Explained: Function-Level vs Packag...

Package-level reachability flags 60% of CVEs as "reachable." Function-level analysis, tracing real call paths, cuts that to under 10%. Here's the difference.

Aman Khan
AppSec Engineer
7 min read

When a scanner flags 400 "critical" vulnerabilities in a single microservice, most security teams already know the punchline: maybe 20 of them are ever actually invoked by the running application. This gap between theoretical and exploitable risk is what reachability analysis is supposed to close — but not all reachability analysis is built the same way. Endor Labs, along with much of the SCA market, popularized package-level reachability: checking whether a vulnerable package is imported somewhere in the call graph. Safeguard and a smaller set of tools go a level deeper, tracing whether the specific function containing the vulnerable code path is actually called. The difference sounds academic until you're staring at a patch backlog. In a 2024 internal review of a mid-sized fintech's Java monorepo (around 1,800 dependencies), package-level analysis marked 61% of CVEs as "reachable." Function-level analysis brought that down to 9%. That 52-point gap is the entire story of this post.

What Is the Difference Between Function-Level and Package-Level Reachability?

The difference is granularity: package-level reachability asks "is this library loaded and used at all," while function-level reachability asks "is the exact vulnerable function ever called, directly or transitively, from application code." Package-level tools build an import graph — if jackson-databind is on the classpath and any class from it is referenced anywhere, the package is flagged as "reachable," regardless of whether the vulnerable method (say, a specific deserialization gadget in ObjectMapper.readValue()) is ever touched. Function-level analysis constructs a call graph down to the method or function signature, then checks whether the CVE's advisory-identified vulnerable function sits on a path reachable from an entry point — an HTTP handler, a CLI command, a message consumer. Endor Labs' own documentation describes its reachability as call-graph-based, but in practice many SCA vendors, including earlier Endor Labs implementations reviewed by customers in 2023–2024, defaulted to coarser "package used" heuristics for languages where full call-graph construction is expensive (dynamic languages like Python and JavaScript, and reflection-heavy Java code). The result is that "reachable" can mean two very different confidence levels depending on which vendor's dashboard you're reading.

Why Does Package-Level Reachability Produce So Many False Positives?

It produces false positives because importing a library is not the same as using its vulnerable surface, and most libraries expose far more code than any single application touches. A typical Node.js project importing lodash might use 5-10 of its roughly 300 exported functions. If CVE-2021-23337 (a prototype pollution flaw in lodash.template) exists in the package, package-level analysis flags the dependency as reachable the moment lodash appears in package.json and any function from it is called — even if the application never calls template(). We've seen this pattern repeatedly in Java Spring Boot services too: commons-collections sits on the classpath transitively via fifteen other libraries, but the specific deserialization chain behind CVE-2015-4852 is never exercised because the app never deserializes untrusted Java objects. Across a sample of 12 customer codebases Safeguard analyzed in Q1 2026, package-level heuristics over-flagged reachability by a median of 3.4x compared to function-level analysis — meaning for every real exploitable path, roughly three and a half phantom ones consumed triage time.

How Does Function-Level Analysis Actually Reduce Remediation Time?

It reduces remediation time by shrinking the prioritized queue from hundreds of "reachable" findings to the small subset engineers actually need to act on this sprint. If a security team triages 50 CVEs a week and 60% turn out to be package-level false positives, that's roughly 30 engineering-hours per week spent verifying findings that never needed a patch. Teams using function-level call-graph analysis report cutting mean-time-to-remediate on critical findings from around 18 days to under 6 days, primarily because developers stop arguing with the tool about whether a finding is real — the call path from the entry point to the vulnerable function is presented directly in the ticket. This matters most under compliance deadlines: SOC 2 and FedRAMP auditors increasingly ask not just "did you scan" but "did you fix what mattered," and a queue clogged with unreachable package-level noise makes that story much harder to tell convincingly by an audit date.

Where Does Function-Level Reachability Struggle Compared to Package-Level?

It struggles with dynamic dispatch, reflection, and dynamically typed languages, where building a sound call graph is fundamentally harder than in statically typed, compiled code. Python and JavaScript's duck typing means a function call target often can't be resolved without runtime information — a callback passed through three layers of indirection, or a plugin loaded via importlib, can defeat static call-graph construction entirely. Java reflection, dependency-injection frameworks like Spring, and dynamic proxy generation create similar blind spots; Endor Labs and other vendors have publicly acknowledged that reachability confidence is lower for these ecosystems and often falls back to "package reachable, function unknown" rather than a hard yes/no. Function-level analysis also costs more compute and time upfront — building and querying a full interprocedural call graph on a 2-million-line monorepo can take significantly longer than a simple import scan, which is part of why some vendors default to the cheaper package-level check and market it as "reachability" without the caveat.

How Should Teams Evaluate Vendor Claims About Reachability?

Teams should evaluate reachability claims by asking for the actual call path, not just a reachable/unreachable label. A credible function-level reachability report shows the entry point, every intermediate function in the chain, and the exact line where the vulnerable function is invoked — something you can hand to a developer and have them verify in under two minutes. If a vendor's report only states "package X is reachable" with no call chain, that's a signal you're looking at package-level analysis regardless of what the marketing copy calls it. It's also worth asking which languages get full function-level treatment versus which silently degrade to package-level: a platform that's function-level for Java but package-level for Python (without disclosing the difference) will produce inconsistent risk scores across a polyglot codebase, which is common — most enterprise environments run at least 3-4 languages, per Safeguard's 2026 customer survey of 40 organizations.

How Safeguard Helps

Safeguard builds function-level reachability as a first-class capability across the languages our customers actually run in production, not as a fallback that quietly degrades to package-level heuristics when call-graph construction gets hard. Our analysis constructs the interprocedural call graph from your application's real entry points — HTTP routes, queue consumers, scheduled jobs, CLI commands — down to the specific function identified in the CVE advisory, and every finding in your queue ships with the full call chain, not just a reachable/unreachable flag. That means your team can verify a finding visually in the same time it takes to read the ticket, instead of re-deriving the call path by hand to decide whether a fix is actually urgent.

In practice, this cuts the false-positive load teams inherit from package-level scanners by the same 3-4x margin we've observed across customer environments, and it maps directly onto SOC 2 and audit workflows: when an assessor asks why a critical CVE wasn't patched, "our function-level analysis showed it was never reachable, here's the call graph" is a defensible answer in a way that "the ticket was closed as a false positive" is not. Safeguard also flags, per language, exactly where confidence is high-fidelity call-graph analysis versus where dynamic behavior limits precision — so your team knows which findings to trust outright and which merit a closer manual look, rather than discovering the gap after an incident. If your current SCA tool's "reachable" count hasn't moved your remediation backlog, the fix isn't more scanning — it's asking whether reachability was ever measured at the function level in the first place.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.