Exploitability and reachability
Severity tells you how bad a bug class is if it is exploited. Vulkro adds two more axes that answer different questions: can this finding 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. |
Two things gate the verdict: 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 combines the finding's sanitiser-gap trace, its
reachability_verdict, and its confidence signals. It does not change severity
(severity stays 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.
Turn the SCA reachability gate on with VULKRO_SCA_REACHABLE=1. Each dependency
finding is then stamped with its reachability_verdict and supporting
entry-point call-chain evidence. An unreachable CVE is downgraded (never
dropped); 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 combines 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. When an outbound HTTP client call in one repo has a
normalised route shape and method matching an exposed endpoint in another repo,
it 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. 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.
None of it changes 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 is unaffected either way: it calls no model, and your code
and findings never leave the machine. 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.