Evidence and the AI layer
Vulkro's scan engine is fully deterministic and runs entirely on your machine. It calls no model: the same input produces the same findings, severities, and exit code every time. On top sits an optional, advisory AI layer that runs locally through Ollama by default.
This guide covers the boundary between the two: the evidence graph that grounds
every AI answer, the advisory tiers (vulkro ai ask, vulkro triage,
vulkro fix --ai), and what a verified AI fix means.
AI assistance is advisory and local by default. It never changes a deterministic scan result: not a finding, not a severity, not a JSON / SARIF / SBOM document, and not an exit code. The published benchmark is AI-free: every number is produced with the AI layer off.
Evidence graph
--format evidence-graph emits a stable, versioned JSON document
(evidence-graph/1.0) that composes endpoints, taint source-to-sink flows,
reachability verdicts, findings, and the dependency SBOM into one graph:
vulkro scan . --format evidence-graph > graph.json
The graph is deterministic ground truth, and it is what grounds AI answers on both sides of the boundary:
- An external agent (your own tooling, an MCP client) can be handed the graph file and reason about it with the scan result as the source of truth.
- Vulkro's own AI layer is grounded in the same structure.
vulkro ai askandvulkro ai chatbuild their context from the scan's evidence graph.vulkro triageassembles each finding's evidence rows, taint trace hops, and reachability verdict into the prompt.vulkro analyze --aiselects its targets from the graph nodes where the engine is structurally weak.
The model never free-associates over your repository: it is asked about what
the deterministic scan saw. vulkro discover . --format evidence-graph
produces the same shape from the discovery pass when you only need the
endpoint and flow structure.
The local AI layer
The AI layer is opt-in and configured once with vulkro ai. It is CLI-only
(no config files); the selection is stored in ~/.vulkro/scans.db and shared
with the vulkro-sf binary, so a model chosen once serves both.
vulkro ai setup # install / point at Ollama, pick a model, self-test
vulkro ai status # current model, runtime, reachability, license tier
vulkro ai use <model> # switch the active model
vulkro ai off # disable the AI layer (deterministic-only)
The default runtime is local Ollama at http://127.0.0.1:11434/v1, and the
default model is qwen2.5-coder:7b (Apache-2.0). On a constrained machine the
setup wizard recommends the smaller phi4-mini:3.8b (MIT) instead. Any
OpenAI-compatible runtime works via --ai-url.
Configuration resolves in a fixed order, highest priority first:
- Per-run flags:
--ai-model,--ai-url,--no-ai. - Environment variables:
VULKRO_AI_MODEL,VULKRO_AI_URL,VULKRO_AI_KEY. - The saved selection from
vulkro ai use. - The smart default above.
| Name | Effect |
|---|---|
VULKRO_AI_MODEL | Model tag override for any AI-enabled command. |
VULKRO_AI_URL | OpenAI-compatible base url override. |
VULKRO_AI_KEY | API key for a bring-your-own-key cloud endpoint. Read from the environment only, never persisted to disk. |
Offline behaviour follows one rule. A loopback (local) endpoint is allowed
even under VULKRO_OFFLINE, because nothing leaves the machine. A cloud
endpoint is refused offline with an actionable error. Cloud is a deliberate,
bring-your-own-key opt-in (vulkro ai setup --byok): pointing the layer at a
cloud url means code context leaves the machine, and Vulkro says so before it
happens. When the endpoint is the Anthropic API, the legacy
VULKRO_TRIAGE_API_KEY / ANTHROPIC_API_KEY variables are still accepted as
key fallbacks.
The advisory tiers
Three tiers cover the ask-understand-fix loop, and every one of them is grounded in the deterministic scan:
Ask: vulkro ai ask and vulkro ai chat
One-shot or interactive questions answered against the current project's scan:
vulkro ai ask "which endpoints accept user input without auth?"
vulkro ai chat
Both run the normal deterministic scan first, then answer only from that
scan's endpoints, findings, and vulnerable dependencies. The answer is
advisory text. Exit code: 0 on success, 2 on error; the advisory answer
never sets exit 1.
Triage: vulkro triage
An advisory second opinion on findings the deterministic engine cannot settle on its own: entitlement / paid-tier bypass, PII / object-property over-exposure, operational reachability of one-off scripts, source trust, and any finding whose reachability the scan could not confidently judge.
vulkro triage .
By default this runs against the local model; no API key is needed. The
verdicts live in a separate report and are never merged back into the scan
result. The JSON output is wrapped in an envelope that states its status
explicitly ("advisory": true, "non_deterministic": true,
"excluded_from_benchmark": true) alongside the provider, model, and endpoint
class that produced it. Exit codes: 0 when no candidates needed triage, 1
when advisory verdicts were reported, 2 on error. See
vulkro triage.
Fix: vulkro fix --ai and vulkro-sf fix --ai
The deterministic vulkro fix templates handle a closed allowlist of safe
rewrites, with no model involved. --ai adds an advisory tier for findings no
template covers: the local model proposes a minimal diff, and the
deterministic engine then judges it (see the verified-fix definition below).
--ai alone is read-only. --ai --write applies only verified diffs, prints
each one first, and runs only after the deterministic templates. A refused AI
fix is a refusal, not an error, and --ai never changes the exit codes of
fix.
For Salesforce, vulkro-sf fix --ai covers the mandatory-review Apex classes
(CRUD / FLS enforcement, with sharing gaps, SOQL injection) with the same
propose-then-verify contract. The model proposes; the deterministic detector
judges.
Beyond the three tiers, the same advisory layer powers optional overlays. Each one is additive, labelled advisory, and changes nothing deterministic:
| Command | What the model adds |
|---|---|
vulkro explain <id> --ai | A plain-language narrative under the unchanged deterministic explanation. |
vulkro diff <base> --ai | A security-delta narrative over the deterministic surface diff. |
vulkro analyze --ai | A graph-guided second pass whose candidates are never merged into the findings. |
vulkro hunt | A model-driven hunt using deterministic checks as tools; a result counts as confirmed only when a deterministic oracle grounds it, otherwise it is a labelled lead. |
vulkro slopcheck --ai | Plain-language explanations of flagged dependency names. |
vulkro-sf scan --ai-explain | Per-finding AppExchange Security Review context, printed to stderr so stdout stays byte-identical. |
What "verified" means
A fix is verified when, after applying the candidate diff:
- the patched file still parses: a patch that introduces new syntax errors relative to the original is refused before any re-scan, so a broken edit can never clear a finding by making the file unparseable, and
- a fresh deterministic re-scan no longer reports the originating finding and reports no new finding.
In the core CLI a second, subordinate AI review then checks the patch looks correct and behaviour-preserving. It can only downgrade a would-be verified fix to refused; it is never consulted when the re-scan failed, so it can never turn a re-scan failure into a pass.
Verified is deliberately narrow. It means the deterministic scanner, re-run from scratch, no longer reports the finding and the file still parses. It does not claim the build compiles or that your tests pass. Review every applied diff the way you would review any patch.
Proof harnesses
vulkro prove turns eligible findings into runnable test files that a
developer can execute to confirm a finding is a true positive:
vulkro prove .
For each injection or broken-authorization finding it emits a harness: a
fast-check property test plus a concrete proof-of-concept for JS/TS, a
Hypothesis and pytest harness for Python, and a generated @isTest class for
Apex (runnable inside a Salesforce org). Vulkro only writes the harness text.
It never compiles or runs it. Findings with no runnable proof shape (config,
inventory, code quality) or in a language without an emitter are listed with a
reason rather than dropped silently. No model is involved.
Cross-repo aggregation
A single repository rarely tells the whole story. vulkro aggregate links
evidence-graph exports from several repositories to surface candidate
cross-repo flows:
vulkro scan service-a . --format evidence-graph > a.json
vulkro scan service-b . --format evidence-graph > b.json
vulkro aggregate a.json b.json
The linker is deterministic: 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. The links are ground truth for an external agent to investigate, not a verdict.
Feed a scan to an agent live
vulkro mcp serve runs Vulkro as a Model Context Protocol server that Claude
Desktop, Cursor, Windsurf, Continue, and other JSON-RPC MCP clients can call
as a tool. This lets an agent request a scan and read the evidence during a
coding session rather than after the fact.
vulkro mcp serve
For in-editor diagnostics, the VS Code extension is built and distributed as a
.vsix; a Marketplace listing is coming soon.
Related
- Exploitability and reachability - what the reachability verdict and exploitability grade mean.
vulkro triage,vulkro fix,vulkro prove,vulkro aggregate,vulkro mcp serve.- Output formats - the full list including
evidence-graph. - Air-gap: offline mode -
VULKRO_OFFLINEand how the loopback AI exception fits the network-free contract.