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:
| Verdict | Meaning |
|---|---|
reachable | The vulnerable or tainted symbol is reached from a project entry-point forward closure (a call-graph join). The most actionable verdict. |
unreachable | The symbol is known but is not reached from any entry point. The finding is degraded on this basis, never dropped. |
unknown | No 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:
| Grade | Meaning |
|---|---|
exploitable | A tainted, unsanitised, user-reachable path to the sink is present: exploitable as written. The most actionable grade. |
likely | A 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. |
theoretical | The 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_verdictandexploitabilityas 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
@isTestclass (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.
Related
- Reachability - the SCA call-shape annotation.
- Dependencies and CVEs - the SCA reachability gate.
- Confidence model - how High / Medium / Low confidence is computed, distinct from exploitability.