Call-graph reachability is the workhorse of modern SCA, and it gets the right answer for most CVE prioritization decisions. The frontier conversation has moved on to semantic reachability, which asks not just whether the vulnerable function is called but whether the preconditions for actual exploitation hold in the deployed configuration. The difference matters for a meaningful subset of CVEs, and the tools that handle it well produce noticeably better prioritization than tools that stop at call-graph reachability.
This post draws the line between the two, walks through the CVE classes where semantic reachability adds real signal, and assesses where the major vendors actually sit on the spectrum. It is written for AppSec leads making procurement decisions and trying to separate genuine capability from marketing claims.
What is the difference, technically?
Call-graph reachability is structural. You build a call graph from your code through your dependencies, you intersect it with the known vulnerable functions in the CVE record, and you report a yes-or-no. The analysis treats every reachable function as equally exploitable, which is wrong in practice but a reasonable first approximation. This is what most reachability tools sell, and it is what produces the 60 to 80% false-positive reductions that have made the technique table stakes.
Semantic reachability adds a layer of analysis about the conditions under which the reachable code path is actually exploitable. For a deserialization CVE, this might mean checking whether the application enables polymorphic type handling. For a buffer overflow, it might mean checking whether the input size validation is in place. For an SSRF, it might mean checking whether the URL allowlist is enforced. The CVE record sometimes describes these preconditions, and a semantic-reachability engine encodes them as additional constraints that have to hold for the finding to be live.
Why does this matter for prioritization?
Consider CVE-2017-9805, the Apache Struts REST plugin XStream deserialization issue. Call-graph reachability flags it as live on any service that uses the REST plugin, which is most Struts deployments of that era. Semantic reachability checks whether the application uses the vulnerable XStream converter with untrusted input, which is a much narrower condition. The first analysis flagged thousands of false positives in the wake of the Equifax breach; the second would have correctly narrowed to the dozens of actually-exploitable cases.
The pattern repeats across the CVE catalog. Deserialization, template injection, and SQL injection bugs frequently require specific feature flags or configuration options to be exploitable. CVE-2022-22965, Spring4Shell, is a famous example: it required a specific combination of JDK version, packaged WAR deployment, and parameter-binding configuration. A reachability tool that flagged every Spring service as exposed was technically correct on call-graph terms and operationally useless. Semantic reachability would have narrowed the alert to the small set of services that actually matched the precondition.
Which tools do semantic reachability well?
The answer in 2026 is: a few, partially, and with caveats. CodeQL has the strongest semantic capability because its query language can express arbitrary preconditions, and Microsoft's vulnerability research team has been publishing CodeQL queries that encode CVE-specific exploitation conditions for several years. Endor Labs ships a feature they call "exploit conditions" that does something similar for a curated set of high-impact CVEs. Snyk Code does it for a smaller set. Semgrep can do it through custom rules, but the burden is on you to write them.
Most other SCA vendors are still at call-graph reachability and call it semantic reachability in marketing materials. The honest test is to ask the vendor how their analysis differs between two Spring services where one binds untrusted parameters into model objects and one does not, for the Spring4Shell case. If the answer is the same flag for both, you are looking at call-graph reachability with a different name.
Where do semantic reachability claims fall apart?
The hard part is that exploitation preconditions are not always documented in the CVE record, and reverse-engineering them from the patch commit is research work. A tool that supports semantic reachability for 50 carefully-chosen CVEs is doing genuinely useful work, but extrapolating that capability to every CVE in the database is dishonest. The reality is that semantic reachability is partial coverage of the most impactful CVEs, not a universal layer.
Configuration is also slippery. A service's runtime configuration can change without the code changing, and a semantic-reachability engine that analyzed the code at build time may not know that production has a different config. Tools that ingest configuration files at scan time and reflect them in the analysis do better, but they introduce a new failure mode where the scanned config does not match the deployed config. The pragmatic answer is to treat semantic reachability as a high-precision signal for the CVEs where it applies, not a universal filter.
How should you operationalize this?
Use call-graph reachability as the primary filter and treat semantic reachability as a prioritization booster on top. A reachable CVE is worth investigating; a reachable-and-semantically-confirmed CVE is worth doing first. Do not invert the relationship and use semantic reachability as a suppressor, because the gaps in semantic coverage will let real risks through.
A reasonable target is to get call-graph reachability working across your entire portfolio first, achieve the 60 to 80% noise reduction that is the baseline, and then layer semantic reachability on the highest-impact CVE classes (deserialization, injection, RCE) where the precondition analysis is well-developed. This is the cadence that mature AppSec programs are settling into in 2026.
How Safeguard Helps
Safeguard runs call-graph reachability across every SBOM your tenant produces, and Griffin AI extends the analysis with exploitation-condition checks for the high-impact CVE classes where preconditions are well-understood. Findings are annotated with the specific configuration or input pattern that makes them exploitable, so engineers can see the difference between "vulnerable function is called" and "vulnerable function is called with the conditions that trigger the bug." Policy gates can enforce on either criterion, and SBOM diffs flag changes in either dimension across builds. TPRM and zero-CVE base images supply the clean foundation; the analysis layer above makes the prioritization defensible.