Application Security

Reachability Analysis in 2025: Separating Exploitable Vulnerabilities from Noise

Reachability analysis determines whether a vulnerable function is actually called by your application. The technology has matured from research concept to production tool. Here is how it works and where it falls short.

James
Application Security Engineer
8 min read

A modern web application might have 1,500 transitive dependencies. A vulnerability scan against those dependencies might surface 200 CVEs. The engineering team has capacity to address maybe 30 this quarter. Which 30?

CVSS says "the ones with the highest severity scores." EPSS says "the ones most likely to be exploited in the wild." Both are useful signals, but neither answers the most fundamental question: is this vulnerability actually exploitable in my application?

That is the question reachability analysis answers.

The Core Concept

Most dependency vulnerabilities exist in specific functions, methods, or code paths within a library. A buffer overflow in a parsing function, a SQL injection in a query builder method, an XML external entity vulnerability in a deserialization routine -- these are localized to particular code.

Your application uses the library, but it may not use the vulnerable part. If you import a utility library for its string manipulation functions, and the library also has a vulnerable XML parser that you never call, the vulnerability exists in your dependency tree but is not exploitable in your application.

Reachability analysis builds a call graph from your application's entry points and determines whether any execution path reaches the vulnerable function. If the vulnerable function is not reachable, the vulnerability is a true finding (it exists in your dependencies) but a false positive in terms of exploitability.

How It Works

Call Graph Construction

The foundation of reachability analysis is the call graph -- a directed graph where nodes represent functions/methods and edges represent calls between them. Building an accurate call graph is the primary technical challenge.

Static call graph analysis examines source code without executing it. The analyzer resolves function calls, method dispatches, and module imports to build the graph. For statically-typed languages like Java and Go, this is relatively straightforward because the type system constrains which methods can be called at each call site. For dynamically-typed languages like Python and JavaScript, it is harder because method resolution depends on runtime types.

Points-to analysis refines the call graph by determining which objects a variable can point to at runtime. This is critical for object-oriented languages where method dispatch depends on the concrete type of the receiver. A call to object.process() could invoke different methods depending on whether object is an instance of ClassA or ClassB.

Entry point identification determines where analysis begins. For a web application, entry points are HTTP route handlers. For a CLI tool, the entry point is the main function. For a library, every public API method is a potential entry point. Missing entry points leads to false negatives (declaring something unreachable when it is actually reachable through an unanalyzed path).

Vulnerable Function Mapping

The second input to reachability analysis is a mapping from CVE identifiers to the specific functions, methods, or code locations that contain the vulnerability. This mapping is more granular than the package-level vulnerability data in the NVD.

Building these mappings requires analyzing each CVE to identify the vulnerable code. Some sources provide this data:

  • Commit diffs for security fixes reveal exactly which functions were modified.
  • Security advisories sometimes reference specific classes or methods.
  • Research databases like the Snyk vulnerability database include function-level vulnerability locations.

The quality of the vulnerable function mapping directly affects the quality of reachability results. Incomplete or incorrect mappings produce false negatives or false positives.

The Reachability Query

With a call graph and vulnerable function mappings, the reachability query is conceptually simple: is there a path in the call graph from any entry point to any vulnerable function?

If yes, the vulnerability is "reachable" -- there exists an execution path from your application to the vulnerable code. This does not guarantee exploitability (there may be input validation or other mitigations along the path), but it establishes that the vulnerable code is in play.

If no, the vulnerability is "unreachable" -- no execution path in your application reaches the vulnerable code. Barring analysis errors, the vulnerability cannot be exploited in your application regardless of its CVSS score.

Accuracy and Limitations

Over-Approximation vs. Under-Approximation

Static analysis faces a fundamental tradeoff. Over-approximation (considering all theoretically possible execution paths) produces false positives -- declaring vulnerabilities reachable when they are not. Under-approximation (considering only definitely-executed paths) produces false negatives -- declaring vulnerabilities unreachable when they are actually reachable.

Most production reachability tools lean toward over-approximation because false negatives (missing a reachable vulnerability) are more dangerous than false positives (requiring unnecessary investigation of an unreachable one). This means reachability analysis reduces false positives significantly but does not eliminate them entirely.

Dynamic Language Challenges

Languages with dynamic dispatch, reflection, eval, and metaprogramming create challenges for static call graph analysis:

JavaScript/TypeScript: Dynamic property access (obj[methodName]()), prototype chain modifications, and eval() can create call edges invisible to static analysis.

Python: Dynamic attribute access (getattr()), metaclasses, and decorators that modify function behavior complicate call graph construction.

Ruby: method_missing, dynamic method definition, and open classes make static call graph analysis particularly difficult.

For these languages, reachability analysis tools typically use heuristics and over-approximation to handle dynamic features. The result is less noise reduction than for statically-typed languages, but still a meaningful improvement over no reachability analysis.

Data Flow vs. Control Flow

Some vulnerabilities are exploitable not through direct function calls but through data flow. Consider a deserialization vulnerability: the vulnerable function might be called by your application, but the data it deserializes comes from a trusted internal source, not from user input. Conversely, a function might be reachable only through a code path that you believe is dead, but an attacker finds a way to trigger it through unexpected input.

Control-flow reachability (is the function callable?) is a necessary but not sufficient condition for exploitability. Data-flow reachability (can attacker-controlled input reach the vulnerable function?) is a stronger signal but harder to compute.

Most current tools provide control-flow reachability. Data-flow-aware reachability is emerging but not yet widespread in production tools.

Framework and Runtime Considerations

Web frameworks often use convention-based routing, dependency injection, and other patterns that create implicit call edges not visible in the source code. A Spring Boot application might invoke a method through its dependency injection container without an explicit call in the source.

Reachability analysis tools need framework-specific models to handle these patterns. The quality of these models varies by tool and framework, and gaps in framework support produce false negatives.

Practical Impact

Despite these limitations, the practical impact of reachability analysis is substantial:

Noise reduction: Organizations consistently report 60-80% reduction in actionable vulnerability findings when reachability analysis is applied. This means 60-80% of the vulnerabilities identified by SCA scanning are in code that the application never executes.

Prioritization accuracy: The remaining 20-40% of vulnerabilities -- those in reachable code -- are much more likely to represent genuine risk. Teams that focus on reachable vulnerabilities first address actual risk faster than teams working through CVSS-sorted lists.

Developer trust: One of the biggest challenges in vulnerability management is developer skepticism. When security files a ticket for a vulnerability in a function the developer knows their code never calls, it erodes trust in the entire process. Reachability-filtered results rebuild that trust.

Adoption Guidance

Start With Your Highest-Volume Languages

If your organization uses multiple languages, start reachability analysis with the languages where your dependency vulnerability volume is highest. For most organizations, this is JavaScript/TypeScript (npm) or Java (Maven). Even imperfect reachability analysis for these ecosystems provides the largest absolute reduction in vulnerability noise.

Validate Against Known Exploits

Before relying on reachability results for prioritization, validate the tool against known cases. Take a vulnerability you know is exploitable in your application and verify the tool marks it as reachable. Take a vulnerability you know is not exploitable and verify the tool marks it as unreachable. This calibration builds confidence in the results.

Combine With Other Signals

Reachability analysis is most powerful in combination with EPSS and CVSS. A vulnerability that is reachable, has a high CVSS score, and has a high EPSS score is a top priority. A vulnerability that is unreachable can be safely deprioritized regardless of its CVSS or EPSS score. The three signals together produce better prioritization than any one alone.

Do Not Ignore Unreachable Vulnerabilities Permanently

Unreachable vulnerabilities should be deprioritized, not ignored. Code changes can make previously unreachable functions reachable. Library updates can change which functions are called internally. Reachability status should be re-evaluated when dependencies are updated or when significant code changes occur.

How Safeguard.sh Helps

Safeguard integrates reachability analysis into its vulnerability scanning pipeline. When a dependency vulnerability is identified, the platform automatically determines whether the vulnerable function is reachable from the application's entry points. Reachable vulnerabilities are flagged for priority remediation, while unreachable vulnerabilities are marked accordingly and deprioritized in the backlog.

The reachability results feed into Safeguard's policy engine, enabling policies like "reachable Critical vulnerabilities must be remediated within 72 hours; unreachable Critical vulnerabilities within 30 days." This approach gives teams the flexibility to address genuine risk urgently while managing unreachable findings on a sustainable timeline. Combined with EPSS scoring and VEX support, Safeguard provides the multi-signal prioritization that makes vulnerability management at scale operationally feasible.

Never miss an update

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