From Noise to Signal: Proving Which Vulnerabilities Your App Actually Calls

Security dashboards are lying to you. That critical CVE flagged in your dependency scan? Odds are, your application never touches the vulnerable code path. The result is alert fatigue, wasted engineering cycles, and a backlog of "vulnerabilities" that pose zero actual risk.
Reachability analysis cuts through the noise. Instead of treating every flagged dependency as a fire drill, it verifies whether a vulnerable component is actually invoked by your application. The concept is straightforward; the implementation is anything but. This guide bridges the gap between high-level theory and the gritty engineering required to make reachability analysis production-ready.
The Core Pipeline: From SBOM to Call Graph
Production-grade reachability is not a single tool you install. It is a multi-stage pipeline that translates package-level metadata into code-level execution paths. Here is the flow that actually works.
1. SBOM Generation: Know Your Inventory
You cannot analyze what you cannot see. Start by generating a Software Bill of Materials (SBOM) that captures direct and transitive dependencies. Tools like Syft excel here, producing a machine-readable inventory of every package in your build artifact or container image. This SBOM is your ground truth—the foundational list of packages that will enter the vulnerability scanning stage.
2. Vulnerability Mapping: Find the Needles
Feed your SBOM into a vulnerability database. OSV.dev is the pragmatic choice: it aggregates advisories from NVD, GitHub Security Advisories (GHSA), and GitLab, and it accepts package name, version, and ecosystem as query inputs. The output is a list of CVEs affecting your dependencies.
At this stage, you have package-level vulnerability data. You know that log4j-core:2.14.1 is bad. You do not yet know if your code ever calls the vulnerable JndiLookup class. That gap is what the next two stages must close.
3. Symbol Resolution: The Critical Bottleneck
Here is where most reachability initiatives stall. You must map each CVE to the specific vulnerable functions or symbols—not just the affected package. Standard databases rarely provide function-level granularity. OSV might tell you the fix commit, but it will not hand you a list of tainted methods.
To resolve symbols, you often need to:
Parse security advisories and commit diffs manually.
Diff the vulnerable version against the patched version to identify changed functions.
Cross-reference with PoC exploits when available.
Without this step, your call graph analysis has no target. Symbol resolution is the bridge between "we use a bad package" and "we call the exact bad function."
4. Static Call Graph Generation: Pathfinding
With vulnerable symbols identified, you now need to determine if your application reaches them. Build a static call graph from your application's entry points—typically API controllers, message queue handlers, or CLI entry functions.
Joern offers multi-language support and is robust for polyglot codebases.
PyCG is purpose-built for Python and handles modern language features well.
Perform a Breadth-First Search (BFS) from each entry point toward the vulnerable symbols. If a path exists, the vulnerability is reachable. If no path exists, the alert is noise, and you can deprioritize it.
Understanding the Dataset Landscape
Your pipeline is only as good as the data feeding it. A crucial distinction exists between datasets that map CVEs to commits and those that map CVEs to functions.
CVE → Commit Datasets: Where the Fix Happened
These datasets identify the commit that patched a vulnerability, but they do not necessarily isolate the root-cause function.
Project KB (by SAP): A high-quality, industry-vetted dataset with strong verified links for Java commits. Use it when you need reliable ground truth for JVM ecosystems.
OSV: Superior to raw NVD data for commit links, but coverage gaps remain. Treat it as a curated feed, not an exhaustive source.
NVD + GHSA: Often noisy. References frequently point to vendor announcements rather than actual code commits. Use these as input feeds; never treat them as ground truth.
CVE → Function Datasets: Where the Bug Lives
Function-level datasets are the holy grail for reachability, but accuracy varies wildly.
Vul4J & RustXec: The gold standard for reproducible research. Each entry includes a Proof-of-Concept (PoC) exploit and build scripts. The trade-off is scale: both are limited to roughly 80–100 CVEs each.
MegaVul: Boasts over 6,000 CVEs, but research from the MONO framework indicates that approximately 31% of entries are non-security patches. You must filter aggressively before using it for reachability modeling.
BigVul / DiverseVul: Large-scale datasets with broad coverage, yet independent audits show label accuracy often drops below 60%. They require heavy cleaning and manual verification before they can drive production decisions.
Why Reachability is an Engineering Nightmare
Even with a clean pipeline and quality datasets, several structural problems make reachability analysis a maintenance burden.
The Bytecode Barrier
In compiled ecosystems like Java, your dependencies arrive as bytecode while your application is analyzed from source. Aligning these into a unified intermediate representation for cross-referencing is non-trivial. Every JDK update, build tool change, or dependency version bump risks breaking the alignment layer.
Inter-Procedural Ambiguity
Research indicates that 89% of real-world vulnerabilities are inter-procedural. The patch might add a safety check in a caller function, while the actual flawed logic remains buried in a callee. If your analysis evaluates functions in isolation, you will miss the context that determines whether the vulnerability is exploitable in your specific call chain.
Incomplete Codebases
Security analysts frequently work with historical vulnerability snapshots that no longer build cleanly. Dependencies have vanished, build scripts have rotted, and the environment has shifted. Tools like JESS address this by using slicing and stub generation to compile methods in isolation, successfully recovering 73% of files from otherwise unbuildable repositories.
Silent Fixes
Not every vulnerability is tracked with a CVE or a clear commit message. Many are patched quietly in routine maintenance releases. Detecting these "silent" fixes requires either advanced graph-based tools like Fixseeker or lightweight statistical filters like Hash4Patch to suppress false positives from already-patched code.
The Bottom Line
Reachability analysis transforms security from a game of whack-a-mole into a prioritized risk roadmap. The engineering hurdles are real—bytecode alignment, inter-procedural reasoning, incomplete snapshots, and silent patches demand significant tooling investment.
Yet the payoff is a pragmatic security posture. When you can demonstrably show that a critical CVE has no call path from your application's surface, you stop burning engineering hours on phantom risks. You focus patching, testing, and incident response on the subset of vulnerabilities that are actually reachable, exploitable, and relevant to your runtime.
That is the difference between being busy and being secure.



