Reachable doesn’t mean exploitable
Software composition analysis started with a simple question: does your application contain a dependency with a known vulnerability?
That question is useful, but it produces a lot of noise. Many vulnerable packages are present in applications that never use them. And, even when an application uses that package, it may never invoke the specific vulnerable function or code path.
Reachability got us closer to what matters. Instead of stopping at “you have this CVE,” reachability asked whether the application can invoke the vulnerable behavior.
But knowing the vulnerable behavior can execute still doesn’t tell you whether an attacker can exploit it.
An exploit is a recipe
We built depthfirst Exploitability around a simple observation: exploits require a set of conditions to hold at the same time.
Imagine a vulnerability whose exploit requires:
- The vulnerable code is present and active.
- An attacker can access the affected entry point.
- The attacker controls data that reaches the vulnerable sink.
- A particular feature or configuration is enabled.
- No validation or sanitization neutralizes the malicious input.
Reachability might establish that the vulnerable behavior can be invoked, but that’s only part of the recipe. If every required condition lines up, there is a viable attack path. If the evidence disproves one, that path is blocked. And if the available evidence can’t settle a condition, the system should say so instead of guessing.
Consider a denial-of-service vulnerability in Django’s redirect handling. An application can invoke the affected redirect code, making the vulnerability reachable. But the exploit depends on Windows-specific URL parsing behavior. If the application runs in Linux containers, that required condition isn’t met. The vulnerable code is reachable, but the application isn’t exploitable through this path.
CVE-2025-64458 Django
Reachability: ReachableExploitability
Not exploitableEach condition below is evaluated against this service to determine whether this vulnerability is exploitable in your environment.
The Windows execution precondition is provably blocked: both services are built and run as Python applications inside Debian Linux containers, whereas this denial-of-service mechanism requires Windows URL-parsing behavior. The direct application redirects also use fixed internal destinations; the behavior of the third-party login-redirect decorator remains unverified, but that uncertainty does not overcome the deployment-level Linux block.
- Attacker access Uncertain An untrusted attacker can invoke an application route or workflow that constructs a Django redirect response from data influenced by that attacker.
- Attacker-controlled data flow Uncertain The attacker-controlled value reaches the redirect target as a very large Unicode URL, especially with a non-ASCII network location/hostname that causes Python URL parsing to perform NFKC normalization.
- Environment matches exploit conditions Not met The vulnerable redirect construction executes on Windows, where Python NFKC normalization is slow enough for the large-Unicode redirect input to create denial-of-service impact.
- Mitigations do not neutralize the exploit Uncertain No pre-sink validation or sanitization rejects, caps, canonicalizes, or replaces the malicious Unicode redirect target before Django constructs the redirect response.
The same distinction can depend on more subtle application behavior. In a Nokogiri vulnerability, attacker-controlled XML can reach the affected parser, so the vulnerable code is again reachable. But exploitation requires the parser to be configured with a specific combination of DTD validation and entity-handling options. If the application doesn’t use that configuration, a required condition is missing and the exploit path is blocked.
GHSA-fq42-c5rg-92c2 Nokogiri
Reachability: ReachableExploitability
Not exploitableEach condition below is evaluated against this service to determine whether this vulnerability is exploitable in your environment.
The XML-processing endpoints are reachable and accept attacker-controlled content, but the inspected parser configuration does not enable the advisory's required DTD-validation state: the explicit path enables entity substitution, while default parsing does not enable DTD validation. No stylesheet-transformation component was found; native-library linkage remains unverified, but the vulnerable data-flow precondition is provably absent from the application code.
- Environment matches exploit conditions Uncertain The runtime is CRuby/native Nokogiri linked to vulnerable packaged or system libraries: libxml2 < 2.9.13 and/or libxslt < 1.1.35, not JRuby or fixed system libraries.
- Attacker access Met An untrusted actor can cause the service to invoke the affected XML parsing or XSLT transformation entry point with content they control.
- Attacker-controlled data flow Not met Attacker content reaches a vulnerable form: an untrusted XSL stylesheet is transformed, or untrusted XML is parsed with DTDVALID enabled and NOENT disabled, containing DTD/ID/entity constructs needed for validation-state corruption.
In both cases, reachability tells us something important: the application exercises vulnerable code. Exploitability asks the next question: do the rest of the conditions required for an attack actually hold?
From vulnerability research to repository evidence
Exploitability evaluates reachability and the remaining conditions required for exploitation together, giving the agent the context to distinguish what code an attacker can exploit.
For each vulnerability, the system determines what exploitation requires. It combines OSV and GHSA data, advisories, proof-of-concept exploits, patches, package source, and additional research to identify vulnerable entry points and translate the exploit into a set of atomic conditions: an exploit recipe.
The agent then evaluates that recipe against the repository at the current commit, considering application code, configuration, threat model, middleware, Dockerfiles, CI, CLI and build usage, and deployment artifacts. It then determines whether the conditions required for exploitation hold.
For every condition, it returns one of three results:
- Met: repository evidence supports the condition required for exploitation.
- Not met: repository evidence demonstrates that the required condition is blocked.
- Uncertain: the available evidence isn’t sufficient to establish either conclusion.
These condition-level results provide the evidence for the assessment, but they don’t always reduce to a simple AND or OR. Exploit surfaces depend on how multiple conditions interact, so the agent reasons across the full set to produce the final reachability and exploitability verdicts.
Uncertainty is important here. If exploitability depends on a WAF rule, runtime environment variable, or third-party behavior not visible in the repository, depthfirst doesn’t assume that control makes the application safe. It marks the condition uncertain, while giving users the ability to verify it with our Agentic Pentester.
Proof of work
A verdict alone isn’t enough, especially when it comes from an agent. depthfirst surfaces the reasoning and direct repository evidence behind each condition so users can evaluate the outcome.
This gives teams the context to audit the verdict and prioritize accordingly: confirmed attack paths first, findings requiring further investigation next, and findings with evidence that rules out an attack path last.
Going beyond reachability
Reachability remains an important part of vulnerability prioritization and makes deeper exploitability analysis practical. Exploitability only runs once vulnerable behavior has been found reachable, then evaluates the additional conditions necessary to turn that behavior into an attack.
This isn’t live exploit execution against a running application. The assessment reasons from the source, configuration, and repository-managed deployment context available at a specific commit. As dependencies, code, configuration, or deployment artifacts change, the assessments refresh.
The goal is to make the strongest exploitability conclusion based on what the available evidence supports, and no stronger. Because “the vulnerable code is reachable” and “an attacker can exploit this application” are two very different things.