Skip to main content

vulkro triage

Advisory only, local model by default, no key needed

Every verdict vulkro triage prints is labelled advisory, non-deterministic, and excluded from the benchmark. It never changes the deterministic scan findings, their severities, or the JSON scan output the benchmark consumes: the benchmark is AI-free. By default the model is a local one served by Ollama on your own machine, so nothing leaves the box and no API key is required.

An opt-in, advisory second opinion. vulkro triage runs a normal deterministic scan, then asks a model for an advisory judgement on the findings determinism cannot settle on its own:

  • the intent-class findings: entitlement / paid-tier bypass, PII / object-property over-exposure, operational reachability of one-off scripts, and source trust
  • findings whose reachability the scan could not confirm (verdict unknown or unreachable): the model re-judges whether a human should still care

The scan engine stays 100% deterministic and calls no model. The advisory verdicts live in a separate report and are never merged back into the scan result.

Usage

vulkro triage [PATH] [FLAGS]

PATH is the project root and defaults to the current directory.

Flags

FlagDescriptionDefault
--ai-model <MODEL>Model tag override (highest precedence). Overrides VULKRO_AI_MODEL, the saved vulkro ai use selection, and the smart default. Example: --ai-model qwen2.5-coder:7b.(smart default, see below)
--ai-url <URL>AI runtime base url override (OpenAI-compatible). A loopback url is allowed under VULKRO_OFFLINE; a cloud url is refused offline.http://127.0.0.1:11434/v1
--model <MODEL>Legacy alias for --ai-model, kept for back-compat. When both are given, --ai-model wins.(none)
--no-aiDisable the AI layer for this run. vulkro triage then exits 2 with a pointer to vulkro scan, which never needs a model.off
--format, -f <FMT>table (human summary) or json (the advisory verdicts in a labelled envelope). Any other format exits 2.table

Model resolution

vulkro triage resolves its model and runtime the same way as every other AI-layer command, highest precedence first:

  1. Per-run flags: --ai-model, --ai-url, --no-ai.
  2. Environment: VULKRO_AI_MODEL, VULKRO_AI_URL, VULKRO_AI_KEY.
  3. The saved selection from vulkro ai use (stored in ~/.vulkro/scans.db; there are no config files).
  4. The smart default: local Ollama at http://127.0.0.1:11434/v1 with qwen2.5-coder:7b, falling back to phi4-mini:3.8b on a constrained machine (fewer than 4 physical cores). The smart default never selects qwen2.5-coder:3b, which ships under a non-commercial license.

vulkro ai off turns the AI layer off persistently; a per-run --ai-model or --ai-url flag (or the matching env var) re-enables it for that run. vulkro ai setup is the one-time wizard that installs the runtime and model.

For a local runtime, triage probes once before scanning: it checks the daemon is up and the model is present (a dead daemon or a missing model is an actionable error before any scan runs), reads the model's real context window so a long finding context is not silently truncated, and self-tests the verdict shape. A model that fails the self-test still runs, with a stderr note that a 7B model is recommended. When the local model advertises JSON-schema output, the verdict is requested as a constrained JSON object; otherwise the plain-text verdict is parsed, and a malformed reply degrades to an uncertain advisory verdict rather than an error.

Cloud endpoints (bring your own key)

The cloud path still exists as the alternative. Any non-loopback --ai-url counts as cloud (loopback means localhost, ::1, or 127.0.0.0/8, so an Ollama box elsewhere on your LAN is classified cloud too):

  • A base url containing anthropic (for example https://api.anthropic.com/v1) routes through the Anthropic Messages client. The key is resolved from VULKRO_AI_KEY, then the legacy VULKRO_TRIAGE_API_KEY, then ANTHROPIC_API_KEY; with none set the command exits 2 with a "set VULKRO_TRIAGE_API_KEY" message. Pick the model id with --ai-model.
  • Any other cloud url uses the generic OpenAI-compatible client; VULKRO_AI_KEY is sent as a bearer token when set.
  • A key is never sent to a local endpoint, and no key is ever written to disk.

Environment variables

NameEffect
VULKRO_AI_MODELModel tag override. Beaten by --ai-model; beats the saved vulkro ai use selection.
VULKRO_AI_URLRuntime base-url override (OpenAI-compatible). Same precedence as VULKRO_AI_MODEL.
VULKRO_AI_KEYCloud API key. Only meaningful for a cloud url: a local endpoint never sends it. Never persisted.
VULKRO_TRIAGE_API_KEYLegacy Anthropic key, honoured only on an Anthropic endpoint when VULKRO_AI_KEY is unset. ANTHROPIC_API_KEY is accepted as a further fallback. Not needed for the default local model.
VULKRO_OFFLINERefuses any cloud AI endpoint (exit 2 with guidance) because that would send data off the machine. The default loopback model keeps working under it: nothing leaves the box. The deterministic scan is unaffected either way.

Exit codes

CodeMeaning
0Scan completed and no findings needed advisory triage.
1Scan completed and advisory verdicts were reported.
2Error: AI layer off (--no-ai or vulkro ai off), local runtime unreachable or model missing, cloud endpoint under VULKRO_OFFLINE, missing key on an Anthropic endpoint, bad path, or API failure.

What the output carries

The json output is wrapped in an envelope that makes its status and its provenance explicit:

{
"advisory": true,
"non_deterministic": true,
"excluded_from_benchmark": true,
"note": "Advisory LLM triage verdicts. These NEVER change the deterministic scan findings, their severities, or the benchmark. Read them as a second opinion only.",
"ai_provider": "ollama",
"ai_model": "qwen2.5-coder:7b",
"ai_endpoint_class": "local",
"total_findings": 0,
"candidates": 0,
"verdicts": []
}

total_findings is the full deterministic count; candidates is how many findings were handed to the model; verdicts is the advisory array. ai_provider is ollama, anthropic, or openai-compat, and ai_endpoint_class is local or cloud, so a pipeline can record after the fact whether anything left the machine. The deterministic findings are never present in, or altered by, this document.

Each run is also recorded, best effort, in the project-local .vulkro/ai/ advisory memory store; browse it with vulkro ai log. A failed write degrades to a note and never affects the run.

Relationship to the scan

vulkro triage is a lens on top of a normal scan, not a replacement for it. The scan it runs is the same deterministic pipeline every other command runs. Triage is one surface of the wider opt-in AI layer (alongside vulkro analyze --ai, vulkro fix --ai, and the vulkro ai commands), and the same invariant covers all of them: AI assistance is advisory, and it never changes a deterministic finding, a severity, or the exit code of vulkro scan. For CI and any reproducible gate, use vulkro scan; reach for vulkro triage only when you want an advisory second opinion on the findings determinism cannot judge.

Examples

# One-time setup of the local runtime and model (interactive wizard).
vulkro ai setup

# Advisory triage of the current project on the default local model.
# No API key needed.
vulkro triage .

# Still works air-gapped: a loopback model sends nothing off the box.
VULKRO_OFFLINE=1 vulkro triage .

# JSON verdicts for a pipeline that records the second opinion separately.
vulkro triage . --format json > triage-advisory.json

# Pin a specific local model for this run.
vulkro triage . --ai-model qwen2.5-coder:7b

# Alternative: bring your own key to a cloud Anthropic endpoint.
export VULKRO_AI_KEY=sk-ant-...
vulkro triage . --ai-url https://api.anthropic.com/v1 --ai-model <claude-model-id>
  • vulkro scan - the deterministic, offline scan that produces the findings triage comments on.
  • Air-gap: offline mode - VULKRO_OFFLINE and why a loopback model is allowed under it while any cloud endpoint is refused.
  • Evidence and AI - the deterministic evidence surfaces an external agent can consume.