Skip to main content

Exploitability and reachability

Severity answers "how bad is this bug class if it is exploited." Vulkro adds two more axes that answer a different question: "how confident are we that this finding can be exploited as written, and is the vulnerable code even reachable." Both are deterministic, computed in a post-pass over the scan, and surfaced as stable output fields.

Reachability as a finding verdict

Each finding can carry a reachability verdict, surfaced as the top-level JSON field reachability_verdict:

VerdictMeaning
reachableThe vulnerable or tainted symbol is reached from a project entry-point forward closure (a call-graph join). The most actionable verdict.
unreachableThe symbol is known but is not reached from any entry point. The finding is degraded on this basis, never dropped.
unknownNo reachability claim was made either way (no symbol map, or insufficient signal). The conservative default.

The verdict is gated by two things: whether the vulnerable symbol is reached from an entry-point closure, and, for taint findings, whether the flow is driven by first-party (in-project) user input. When neither can be established, the verdict stays unknown rather than guessing.

Exploitability grading

On top of reachability, Vulkro composes an exploitability grade, surfaced as the top-level JSON field exploitability and as a SARIF property:

GradeMeaning
exploitableA tainted, unsanitised, user-reachable path to the sink is present: exploitable as written. The most actionable grade.
likelyA strong signal that the finding is real, but at least one exploit precondition is unproven (reachability is unknown, or a sanitiser may cover the gap). Worth fixing, not a confirmed exploit.
theoreticalThe pattern is present but exploitation is not demonstrated: the sink is unreachable, a sanitiser covers the gap, or confidence is low. Keep it visible for audit, deprioritise for triage.

The grade is composed from the finding's sanitiser-gap trace, its reachability_verdict, and its confidence signals. It does not change severity - severity remains the bug-class impact - it changes triage order: work the exploitable findings first, then likely, then theoretical.

Where it appears

  • Table output annotates each finding with its grade.
  • JSON carries reachability_verdict and exploitability as top-level fields on every finding, so CI gates and dashboards can sort on them.
  • SARIF carries exploitability as a property.
  • The desktop console (vulkro serve) sorts and filters the findings table on these fields.

How SCA uses reachability

For dependency-CVE (SCA) findings, the reachability verdict drives a downgrade. With the SCA reachability gate on (VULKRO_SCA_REACHABLE=1), each dependency finding is stamped with its reachability_verdict and supporting entry-point call-chain evidence; an unreachable CVE is downgraded (never disappeared) and a reachable CVE is pinned to its public severity. This is the same mechanism the Dependencies and CVEs and Reachability pages describe from the dependency side.

Proof harnesses (vulkro prove)

For injection and broken-authorization findings, Vulkro can emit a runnable test that a developer runs to confirm the finding is a true positive. Vulkro only generates the harness text - it never compiles or runs it.

  • JavaScript / TypeScript: a fast-check property test plus a concrete proof-of-concept.
  • Python: a Hypothesis + pytest harness.
  • Apex: a generated @isTest class (runnable only inside a Salesforce org).

Findings with no runnable proof shape (config, inventory, code quality) or in a language without an emitter are skipped and listed with a reason - nothing is dropped silently.

vulkro prove . # print harness sources to stdout
vulkro prove . --format emit # write one file per finding under .vulkro-proofs

Exit codes: 0 no harness emitted (no eligible findings), 1 at least one harness emitted, 2 error. See vulkro prove.

Evidence graph and cross-repo aggregation

The evidence-graph output format composes endpoints, taint source-to-sink flows, reachability verdicts, findings, and the dependency SBOM into one stable, versioned JSON document (evidence-graph/1.0):

vulkro scan . --format evidence-graph > repo.evidence.json

It is deterministic ground truth meant to be handed to an external AI agent - Vulkro embeds no model; the agent brings its own. vulkro aggregate then links the evidence graphs of several repositories: an outbound HTTP client call in one repo whose normalised route shape and method match an exposed endpoint in another repo is emitted as a candidate cross-repo link for the agent to investigate.

vulkro aggregate repoA.evidence.json repoB.evidence.json

Advisory triage lens (vulkro triage)

vulkro triage is the only part of Vulkro that calls a model, and it is opt-in and bring-your-own-key. It runs a normal deterministic scan, then asks a model for an advisory second opinion on the intent-class findings determinism cannot judge on its own (entitlement / paid-tier bypass, PII over-exposure, operational reachability of one-off scripts, source trust).

Every verdict it prints is labelled advisory and excluded from the benchmark, and none of it mutates the deterministic finding severity or the JSON scan output. It requires your own key in VULKRO_TRIAGE_API_KEY (with ANTHROPIC_API_KEY accepted as a fallback), and it refuses to run under VULKRO_OFFLINE=1. The deterministic scan stays fully offline regardless. See vulkro triage.