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

Search for a command to run...

No comments yet. Be the first to comment.
Harbor is an enterprise-class private container registry with advanced features like RBAC, replication, vulnerability scanning, and more. In this guide, we'll set up Harbor on a Ubuntu machine, secure it with Let's Encrypt certificates, and use Cloud...

Introduction This document provides a step-by-step guide to installing and configuring the AWS CLI and Pulumi on Ubuntu 24.04. Prerequisites Ubuntu 24.04 installed and configured. A user account with sudo privileges. An active AWS account. 1. In...

What motivated me to do this? While reading the book Container Security, a strange question popped into my head. Since everything inside a container is essentially a process running on the host machine's Linux kernel, what about creating nested conta...

Introduction This document outlines the procedures for creating AWS Access Keys for both IAM users and IAM Identity Center (Federated) Users. These credentials are used for programmatic access to AWS services, enabling applications and tools to inter...

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.
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.
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.
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.
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."
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.
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.
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.
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.
Even with a clean pipeline and quality datasets, several structural problems make reachability analysis a maintenance burden.
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.
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.
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.
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.
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.