Skip to main content

vulkro scan

The full security pipeline: endpoint extraction + OWASP API Top 10:2023 + secrets + dependencies (CVE) + taint + reachability + privacy + IaC + git history. Exit 1 on any Critical/High finding (after --min-confidence).

Usage

vulkro scan [PATH] [FLAGS]

Arguments

ArgumentDescriptionDefault
PATHProject root..

Flags

FlagDescription
--format, -f <FMT>table, json, sarif, gh-pr, junit, csv, cyclonedx, spdx, pdf, ropa-md, ropa-html. Default: table.
--verbose, -vShow file paths and line numbers per finding.
--savePersist the run to ~/.vulkro/scans.db and snapshot trends.
--baseline <FILE>Compare findings against a saved baseline JSON; only emit new findings. See also: Baselines explained.
--rules <PATH>Extra rule pack file or directory of *.toml files.
--profile <NAME>Attach a compliance evaluation: owasp-asvs, pci, soc2.
--min-confidence <LEVEL>medium (default), high, low. Filters before exit-code computation.
--all-confidence (alias --all)Equivalent to --min-confidence low. Surfaces every heuristic finding.
--min-severity <SEV>critical, high, medium, low, info. Off by default (every severity shown). Drops findings below the given level from the output: a display floor, not a gate, so --fail-on still evaluates the filtered set. The CLI equivalent of the VS Code extension's severityThreshold setting - run CI with the same value to see exactly the set your editor shows. A stderr note reports how many findings were dropped.
--triageOFF by default. Offline, deterministic false-positive triage: demotes findings in test / fixture / example / generated / vendored / migration paths one confidence tier. No network, no model. Pair with --min-confidence high to push that noise out of the default view. See Confidence model.
--gateApply the [quality_gate] section of vulkro.toml. Replaces the default Critical/High exit policy.
--validate-secretsOFF by default. When set, each detected secret is probed against its provider API and tagged [live], [dormant], or [unknown].
--gate-vs <REF>Restrict findings to lines that changed vs the given git ref. The exit code reflects only diff-scoped findings - the "block PRs that introduce new issues" lane. Part of Pro (Release gate); --fail-on and a plain --gate stay Free.
--scope <KIND>all (default) shows every finding. src drops Dockerfile / lockfile / IaC / template / docs findings while keeping .env* and k8s manifests.

Presets

vulkro scan exposes three named profiles that bundle a set of flag defaults appropriate for a specific lane. Two equivalent surface forms:

vulkro scan quick . # positional preset
vulkro scan --preset quick . # named flag
PresetLaneBundles
quickPre-commit hook--min-confidence high, --scope src, --since HEAD~1. Fast diff-scoped scan over source files.
ciCI gateCurrent default behaviour plus --gate and a default --fail-on critical,high exit policy.
deepPre-release review--all-confidence, --include-unreachable, --min-evidence 0.0. Maximum recall.

Bare vulkro scan [path] defaults to the ci preset and emits a one-time stderr hint suggesting an explicit form so the chosen lane is visible in CI logs. Suppress the hint with VULKRO_QUIET_PRESET_HINT=1.

Precedence: a user-passed flag always wins over the preset's value. vulkro scan quick . --min-confidence low actually scans at low, even though quick would otherwise raise the floor to high. For boolean flags (--gate, --all, --include-unreachable) the preset can only turn them on; there is no --no-gate style negation.

vulkro scan quick . # pre-commit hook lane
vulkro scan ci . # what CI should do
vulkro scan deep . # release-review thoroughness
vulkro scan . # defaults to `ci`, prints one-time hint
VULKRO_QUIET_PRESET_HINT=1 vulkro scan . # quieter CI logs

An unknown preset name (vulkro scan --preset XYZ) exits 2 with an actionable error listing the valid presets.

Strict confidence

--strict-confidence (or VULKRO_STRICT_CONFIDENCE=1) filters High-confidence findings whose evidence bag is empty. Off by default - behaviour-preserving for callers who don't opt in. Intended for CI gates that want maximal precision: every surviving High finding has cumulative-evidence proof per the confidence rubric (a taint-flow source-to-sink, an exact-match invariant, a schema-vs-code contradiction, or corroborating signals whose weights sum past the threshold).

vulkro scan ci . --strict-confidence # CLI flag
VULKRO_STRICT_CONFIDENCE=1 vulkro scan ci . # env var

The CLI flag wins if both are set. Use this when a CI pipeline should only fail on findings with explicit evidence and tolerate some loss of recall in exchange.

Post-quantum crypto audit

--pq-audit runs an opt-in audit pack that flags classical RSA, ECDSA, ECDH, and classical Diffie-Hellman usage that NIST PQC migration guidance targets for replacement:

  • FIPS 203 (ML-KEM, the Kyber-derived key-encapsulation standard)
  • FIPS 204 (ML-DSA, the Dilithium-derived digital-signature standard)
  • FIPS 205 (SLH-DSA, the SPHINCS+-derived stateless hash-based signature standard)

Symmetric primitives (AES, ChaCha20, SHA-2 / SHA-3) are deliberately NOT flagged: Grover's algorithm only halves effective key length, so AES-256 stays 128-bit-strong against a cryptographically-relevant quantum computer (CRQC). Doubling key length where you already use a symmetric primitive is the correct mitigation, not replacing the algorithm.

vulkro scan ci . --pq-audit

Off by default. PQ findings are a planning surface, not an exploit-today defect; opting in keeps the default scorecard focused on actionable risk.

Confidence model

The default is --min-confidence medium - Medium and High findings emit; Low-confidence heuristic findings (test/example/migration code matches, pattern-only fires without corroboration) are hidden unless explicitly requested. Vulkro is tuned for high recall at the engine level and graduated precision at the display level.

vulkro scan . # medium + high (default - typical day-to-day)
vulkro scan . --min-confidence high # high only - production-recommended for CI gates
vulkro scan . --all-confidence # everything, including Low (audit / debug mode)

At --min-confidence high Vulkro leads the public vulnerability corpus on precision, recall, and F1. The exact head-to-head figures are published on the benchmark page, reproduced from the committed scorecards. See Confidence model for the calibration table and the cumulative-evidence aggregator that governs how detector findings clear each tier.

Inline suppression

Add a comment in the source instead of a config file:

# vulkro:disable next-line API2
@router.get("/health")
def health(): ...
// vulkro:disable next-line API2
app.get("/internal/ping", noAuth);

/* vulkro:disable next-line API1 */
db.query(`SELECT * FROM ${table}`);

// vulkro:disable-file at the top silences every finding in that file. The <rule-id> is a kebab-case detector signal (for example cors-wildcard) or an OWASP API code (for example API1). Suppression counts are reported in the scan summary so you can audit them. See the suppressions guide for the full syntax, vulkro.toml [[suppress]] blocks with expires, and [team_policy].

Diff-scoped scans

The diff-scoped family is part of Pro: vulkro gate, scan --gate-vs and the ratchet (Release gate, release-gate). A plain severity exit policy (--fail-on, --gate) is Free. See Pricing.

Block PRs only on new findings vs a base ref:

vulkro scan . --gate-vs main

Findings outside the changed-line set are still reported in JSON for completeness but the exit code reflects only diff-scoped findings. Combine with --baseline for richer comparisons.

Persisting runs

vulkro scan . --save
vulkro history
vulkro diff main
vulkro trends

Scan history, trends and compare are part of Pro; --save itself is Free.

State lives at ~/.vulkro/scans.db (SQLite). vulkro diff matches findings by stable finding_key so re-orderings, whitespace changes, and refactors don't trip the diff.

Examples

# Default - colourised summary, exit 1 on Critical/High.
vulkro scan .

# CI: SARIF for code-scanning + a separate gating call.
vulkro scan . --format sarif > vulkro.sarif
vulkro scan . --min-confidence high # gate

# PR-scoped review.
vulkro scan . --gate-vs origin/main --format gh-pr > comment.md

# Gather everything for an audit, scoped to source code only.
vulkro scan . --all-confidence --scope src --format json > audit.json

Command reference

Generated from vulkro help scan on vulkro 0.26.0. This block is the authoritative flag, usage, and exit-code reference for this command; the prose above is the friendly explanation. Do not edit this block by hand; run npm run docs:cli after a release.

Scan a project for security findings.

Run a full security scan (endpoint extraction + OWASP API Top 10 checks)

Two equivalent forms: * `vulkro scan <preset> <path>` - positional preset (e.g. `vulkro scan ci .`) * `vulkro scan --preset <p> [path]` - named preset flag

Bare `vulkro scan [path]` defaults to the `ci` preset and emits a one-time stderr hint suggesting an explicit form.

The first successful scan on a machine additionally prints a one-time next-steps note to stderr (triage commands, changed-file rescan, dashboard). It is suppressed when stderr is not a TTY, when `--format` is any machine-readable format, or when the `CI` environment variable is set; a marker file under `~/.vulkro` guarantees it never prints twice. Presets:

* `quick` - `--min-confidence high`, `--scope src`, `--since HEAD~1`. Fast pre-commit hook scan. * `ci` - current default behaviour plus `--gate` and a default fail-on critical+high exit policy. What CI should do. * `deep` - `--all-confidence`, `--include-unreachable`, `--min-evidence 0.0`. Pre-release thoroughness.

Precedence: a user-passed flag always wins over the preset's value (so `vulkro scan quick . --min-confidence low` actually scans at `low`, even though `quick` would otherwise raise the floor to `high`). For boolean flags (`--gate`, `--all`, `--include-unreachable`) the preset can only turn them on; the user can also turn them on, but there is no `--no-gate` style negation today.

Environment variables honoured by `scan`: * `VULKRO_QUIET_PRESET_HINT=1` - suppress the one-time stderr hint that fires when bare `vulkro scan [path]` runs without an explicit preset. CI logs stay uncluttered for teams who already standardised on the bare form. * `VULKRO_STRICT_CONFIDENCE=1` - equivalent to passing `--strict-confidence`. Filters High findings whose evidence bag is empty, for CI gates that want maximal precision. * `VULKRO_NEXTJS_FULL_MODULES=1` - in a Next.js project, register every source file with all detectors rather than only the route-shaped ones. Off by default: measured at roughly 6% true positives on the new High and Critical rows across three real apps, and it about doubles scan time. Route recognition itself (route groups, non-api Route Handlers) is always on regardless. * `VULKRO_DISABLE_CACHE=1` - equivalent to `--no-cache`. * `VULKRO_LEGACY_UNCONFIRMED_TAINT=1` - DEPRECATED, removed in the next release. Restores the pre-0.22 behaviour where an injection finding whose taint check failed was promoted back above the default confidence floor by the evidence aggregator, despite the rule having demoted it. Those rows are Critical and therefore inside the set `--fail-on` evaluates, so set this only to keep an existing pipeline's exit code stable while you re-baseline. * `VULKRO_LEGACY_TIER=1` - DEPRECATED, removed in the next release. Restores the pre-2026-08 tier, where a finding in a test / example / fixture / migration / generated / doc / dev-tool / framework-internal file could still be Critical or High. Such a finding is now capped at Medium severity, which removes it from the default `--fail-on critical,high` set. Set this only to keep an existing pipeline's exit code stable while you re-baseline. A real provider-format secret (AKIA / sk_live_ / ghp_) keeps its severity regardless. * `VULKRO_STRICT_PROOF` - the strict-proof tier, ON BY DEFAULT. A Medium finding that rests only on heuristic pattern signals (no tenant-scope filter, ORM N+1, missing rate limiter, exposed stack trace) with no dataflow, secret-format, or reachability proof is demoted to Low, so the default view shows only proof-backed findings. The demoted rows stay visible under `--all-confidence`. Set `VULKRO_STRICT_PROOF=0` to restore the fuller default view (the heuristic-only Medium rows re-enter `--fail-on critical,high,medium`). * `VULKRO_VERIFIED_REQUIRES_PROOF` - OFF by default. Changes what the top `Verified` confidence tier means. By default `Verified` is a handler-window co-occurrence signal (auth finding on an unprotected route whose handler reads user input), which outranks findings that carry a real source-to-sink taint trace (those sit at `High`). Set this flag truthy to reserve `Verified` for findings with a machine-checkable proof (a taint / sanitizer-gap trace hop, or the `admitted` disposition); the co-occurrence findings then stay at `High` with an honest reason. Unset (default) keeps the legacy promotion, so a default scan is unchanged. * `VULKRO_GIT_BLAME=1` - populate per-finding `owner` via `git blame` of the offending line. * `VULKRO_TAINT_P7_*=1` - opt in to data-layer SQL sink-depth recognition (all off by default; see the README env-var section). `P7_TAG_GATE` clears parameterising `sql``` tags / placeholder expansion / bound values; `P7_KNEX_RAW`, `P7_ORM_RAW_ESCAPE`, `P7_THIS_HANDLE`, `P7_RECEIVER_NAMES` widen JS/TS raw-SQL sink recognition (each also activates the gate); `P7_QB_IDENT` adds tainted ORDER BY / GROUP BY identifier slots as a DEMOTED (Low) finding; `P7_PY_RAW` and `P7_JVGO_RAW` add Python and Java/Go raw-SQL sinks. `P7_HEURISTIC_SQL_GATE` (on by default, set to `0` to opt out) suppresses a provably parameterised INJ-001 store-then-execute false positive. * `VULKRO_TAINT_VALUE_DEFUSE=1` - opt in to value-based def-use taint for the JS/TS backend (off by default; a default scan is byte-identical). A local carries taint only when the assignment value structurally preserves it (concat, template interpolation, property / index read, array / spread, ternary). A value laundered through a value-DESTROYING call (`sha1(...)`, `parseInt`, `.length`) is cleared; a value-PRESERVING reshape (`.toString()`, `Buffer.from`, `atob`, `JSON.parse`, `.toLowerCase`) is treated as pass-through so a merely-decoded request value still fires. Experimental precision flag. * `VULKRO_TAINT_CALLBACK_PARAM=1` - opt in to binding the parameter of a higher-order iteration callback in JS/TS (off by default; a default scan is byte-identical). `.map`, `.forEach`, `.filter`, `.flatMap`, `.find`, `.findLast`, `.findIndex`, `.some` and `.every` pass an ELEMENT in the callback's first slot, so the parameter inherits the receiver's taint and a query built inside `items.map(row => ...)` reaches the sink. `.reduce` and `.sort` are never matched: their first parameter is an accumulator / comparator, not an element. Off by default because the binder has no representation for the callback's SCOPE, so the name stays bound for the whole enclosing function and a handler that reuses a short parameter name across several callbacks can report a flow that does not exist. Experimental recall flag. * `VULKRO_XPKG_JVM=1` - opt in to Java cross-file call-graph edges (import / same-package resolution + `method_invocation` call sites, strict unique-declarer binding). Off by default; see the README env-var section. Feeds taint-interproc / reachability / SCA. * `VULKRO_TAINT_FLOW_ORDER=0` - opt OUT of the intra-method statement-ordering gate (ON by default) in the proof-carrying taint backends (JS/TS, Python, Go, Java). With the gate on, a trace is emitted only when the assignment that taints the value sits above the sink it reaches. A lexical escape waives that where textual order says nothing about execution order (a definition below the sink in the SAME loop body, or outside a closure the sink is in). Set to `0` to reproduce the pre-gate answer exactly. * `VULKRO_REACHABILITY_CLOSURE=1` - opt in to the reachability-closure repair (off by default). Widens the forward call-graph closure. It stays off until the suppression threshold it feeds (`MIN_TRUSTED_COVERAGE_PERCENT`) is validated, because the one time that gate armed it only deleted true positives. Changes AST-level extraction, so pair with `VULKRO_DISABLE_CACHE=1` when measuring. * `VULKRO_INSPECT_DEPS=1` - opt in to inspecting the installed source of your REACHED npm dependencies for compromised-package payloads (mal_gate + mal_loader over `node_modules/<pkg>` for reached deps only). Off by default; see the README env-var section. * `VULKRO_SCA_NO_TRANSITIVE=1` - stop enumerating the lockfile dependency tree. By default a scan matches every unique `(name, version)` in the lockfiles that govern your manifests (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `poetry.lock`, `Pipfile.lock`) against the local CVE bundle, and reports hits that no manifest declares at Medium confidence, marked `(transitive dependency)`. Set this to report only the dependencies your manifests declare. Transitive matching is always local: those packages are never sent to `api.osv.dev`, even under `VULKRO_CVE_LIVE=1`. * `VULKRO_CVE_LIVE=1` - opt in to the live CVE lookup, which sends each dependency's package name, version, and ecosystem to `api.osv.dev` to catch CVEs published since your local bundle. OFF by default: a plain scan resolves dependencies against the local bundle only, so nothing about your dependency tree leaves the machine. Run `vulkro update` to refresh the local bundle instead. Ignored under `VULKRO_OFFLINE=1`. * `VULKRO_OFFLINE=1` - disable any outbound HTTP call (CVE feed, live CVE lookup, secret validation). Also disables the heartbeat. A scan never contacts the release feed: the version check runs only inside `vulkro update`. * `VULKRO_NO_HEARTBEAT=1` - disable only the usage heartbeat (any non-empty value) while leaving the rest of online licensing and other network calls untouched. * `VULKRO_HEARTBEAT_URL` - override the heartbeat endpoint URL (defaults to `https://api.vulkro.com/v1/heartbeat`). Useful for self-hosting or testing. * `VULKRO_EDITOR` / `VULKRO_EDITOR_VERSION` - the editor and its extension version (`vscode` / `cursor` / `windsurf` / `codium`, e.g. `0.4.1`) that launched the scan, so the run is attributed to an IDE rather than a terminal. Set automatically by the Vulkro editor extension; a plain terminal run leaves them unset and reports as `cli`. * `VULKRO_DB` - full path to the local SQLite store, replacing the default `~/.vulkro/scans.db`. One file holds the per-file extraction cache, the scan history and trend snapshots, and local licence state, so this redirects all of them together. The parent directory is created if it does not exist. * `VULKRO_AUTH_MIDDLEWARE_NAMES` - comma-separated list of project-specific auth-middleware function names to recognise. * `VULKRO_AUTH_HELPER_NAMES` - comma-separated list of project-specific auth-helper function names to recognise. * `VULKRO_MONOREPO_SUBDIRS` - comma-separated list of subdirs to treat as monorepo workspace roots. * `VULKRO_SCA_REACHABLE=1` - opt in to the reachability gate for dependency-CVE (SCA) findings. Each dep-CVE finding is stamped with a `reachability_verdict` (reachable / unreachable) and supporting entry-point call-chain evidence; unreachable CVEs are downgraded. Off by default because it needs the call graph. * `VULKRO_SCAN_BUDGET_SECS` - opt-in wall-clock ceiling for the detector-pass phase, in seconds (integer or fractional). Unset or `0` means unlimited, which is the default and changes nothing. When set, the budget is checked only at each pass boundary (never mid-pass): once the elapsed time reaches it the scanner stops dispatching further passes, names every skipped pass in the JSON / SARIF `degraded_passes` field, and adds a partial-results line to `warnings`. Use it to stop a very large repository from hanging a CI `--gate` run; the results are partial, so raise the budget or scan a subdirectory to get the full pass-set back. Exit codes are unchanged: a partial scan that still has findings is `1`. * `VULKRO_PARSE_CACHE_MAX_BYTES` - admission cap, in source bytes, for the per-scan parse cache (default 64 MiB). The scanner parses each file once and reuses the tree across passes; this bounds cache memory on a very large monorepo. Files past the cap fall back to a fresh parse, so findings are identical whatever the cap. * `VULKRO_DISABLE_PARALLEL_DISPATCH=1` - run the independent detector passes serially instead of in parallel across worker threads. Output is byte-identical either way (the parallel run reassembles findings in a fixed order). Unset (parallel) is the faster default. * `VULKRO_DETERMINISTIC=1` - pin the two wall-clock fields in the JSON output (`scanned_at` and `reachability.graph_built_at`) to a fixed instant so the full `--format json` document is byte-reproducible run to run (a baseline-diff / CI-gating aid). Ids and ordering are always deterministic regardless of this flag; only the two clock fields depend on it. `SOURCE_DATE_EPOCH` (the reproducible-builds standard, integer seconds since the Unix epoch) also enables pinning and sets the exact instant. Unset (default) keeps the real scan time. * `VULKRO_AUTOFIX_WIDE=1` - widen the one-click `suggestion` blocks in the `gh-pr-inline` output from the four read-only fix classes (CORS wildcard, JWT verify-off, debug flag, hardcoded secret) to the fuller deterministic catalog (parameterised SQL, cookie flags, weak hash / random, yaml.safe_load, XXE flags, JWT alg:none, Django cookie settings, HTTPS upgrade). Every widened suggestion stays fully offline and deterministic, and is emitted only for a proof-carrying (admitted) finding whose rewrite a re-scan confirms clears it. Off by default, so a plain scan's PR output is byte-for-byte unchanged.

Exit codes: `0` no findings at or above --fail-on, `1` findings reported (or gate / ratchet hard-block), `2` arg error or internal failure.

Usage: vulkro scan [OPTIONS] [FIRST_POS] [SECOND_POS]

Arguments:
[FIRST_POS]
First positional. Either a preset name (`quick`, `ci`, `deep`) or the path to scan. If it matches a preset name, the next positional is the path; otherwise this IS the path with the default `ci` preset

[SECOND_POS]
Second positional. Only consulted when the first positional is a recognised preset name. Defaults to `.` (current directory)

Options:
--offline
Hard-disable every outbound network call for this run (sets VULKRO_OFFLINE=1). Blocks the CVE feed, the license heartbeat, the update check, webhooks, and any cloud AI endpoint; a loopback model (http://127.0.0.1, http://localhost) is still allowed. Equivalent to exporting VULKRO_OFFLINE=1, and the flag wins when both are set

--preset <PRESET>
Explicit preset selector. Takes precedence over a preset-named first positional; `--preset ci .` ignores the positional preset slot and treats the next positional as a path. Valid: `quick`, `ci`, `deep`. Unknown values exit 2 with an error

-f, --format <FORMAT>
Output format

Possible values:
- table
- json
- sarif
- gh-pr
- gh-pr-inline-comments: GitHub PR per-finding **inline review comments**. NDJSON, one `{path, line, side, severity, rule_id, fingerprint, body}` object per line. Designed to be piped straight to a `gh api` loop so `vulkro gate` can drop comments next to the offending line on the Files Changed tab without going through a GitHub App. See the GitHub CLI integration guide at vulkro.com/docs
- github-annotations: GitHub Actions / GitLab CI **PR annotations**. One workflow-command line per finding (`::error file=...,line=...,endLine=...,title=<rule id>::<message> (<helpUri>)`). Printed straight to a CI job's stdout, GitHub turns each line into an inline annotation pinned to `file:line` on the Files Changed tab; GitLab CI's annotation parser reads the same grammar. When run against a baseline (`--gate-vs` / a `gate` flow) only NEW findings are annotated so a first run does not paper the PR
- gitlab-mr: GitLab Merge-Request comment: GitLab-flavored Markdown with collapsible blocks
- bitbucket-pr: Bitbucket Pull-Request comment: flat Markdown (Bitbucket does not render `<details>`)
- azure-pr: Azure DevOps Pull-Request comment: flat Markdown (shared with Bitbucket)
- junit
- csv
- cyclonedx: CycloneDX 1.6 JSON SBOM (uses `ScanResult.packages`)
- cyclonedx-1.7: CycloneDX 1.7 JSON SBOM. Same component shape as `cyclonedx`; emits the newer `specVersion`. Offered alongside 1.6 (which stays default)
- spdx: SPDX 2.3 JSON SBOM
- spdx3: SPDX 3.0.1 JSON-LD SBOM (`@context` + `@graph` of typed elements). Offered alongside SPDX 2.3, which stays the default for `--format spdx`
- cbom: CycloneDX 1.6 CBOM (Cryptographic Bill of Materials): one `cryptographic-asset` component per detected weak algorithm (MD5, SHA-1, ECB, RC4, DES, static IV, insecure RNG), with file:line occurrences inlined under `evidence.occurrences`. Compliance buyers (FedRAMP, post-quantum readiness reviews) ask for this as a distinct artefact from the library SBOM
- openvex: OpenVEX 0.2.0 exploitability statements for each dependency CVE (`affected` / `not_affected` / `under_investigation`), with `not_affected` backed by reachability analysis. Pairs with an SBOM
- cyclonedx-vex: CycloneDX 1.6 VEX: the same exploitability verdicts as `openvex`, in a CycloneDX `vulnerabilities[].analysis` document
- cbom-1.7: CycloneDX 1.7 CBOM. Same crypto-asset grouping as `cbom` plus a richer post-quantum descriptor. Offered alongside 1.6 (default)
- pdf: PDF render of the executive HTML report (requires `wkhtmltopdf` on PATH)
- ropa-md: GDPR Article 30 Records-of-Processing template - Markdown
- ropa-html: GDPR Article 30 Records-of-Processing template - HTML
- ndjson: Newline-delimited JSON: one finding per line plus a trailing summary line. Designed for SIEM ingestion and `jq` filter pipelines
- evidence-graph: Evidence-graph JSON (`evidence-graph/1.0`): a stable, versioned, AI-consumable document that composes endpoints, taint source -> sink flows, reachability verdicts, findings, and the dependency SBOM into one graph. Meant to be handed to an external AI agent as deterministic ground truth (Vulkro embeds no model; the agent brings its own). Pairs with `vulkro aggregate` for cross-repo linking. See `docs/ai-tool/`

[default: table]

-v, --verbose
Show file paths and line numbers for each endpoint

--save
Save results to local database (~/.vulkro/scans.db) and persist a trend snapshot

--web
Scan, save the result to the console database, then open the web console

--baseline <FILE>
Compare findings against a saved JSON baseline; emit only new findings in the gh-pr formatter and exit non-zero only on regressions

--rules <PATH>
Extra rule pack file or directory of *.toml files

--rule-pack <NAME>
Enable a built-in opt-in rule pack by name. Pass multiple times to enable several. Detector packs (each runs a real detector module): `business_logic` and `money_handling` (BL-* / FIN-* business-logic and money-flow checks), `state_machine` (SM-* checks), `concurrency` (CONC-* checks).

Why opt-in: these rules trade precision for coverage of domain-specific concerns (jurisdiction-dependent tax/discount ordering, idempotency-key conventions, state-transition gaps, race windows, etc.). The default scan stays high-precision; project leads explicitly enable the packs that apply to their stack. An unrecognised pack name prints a warning listing the valid names; it does not abort the scan.

--profile <PROFILE>
Attach a compliance evaluation (owasp-asvs | pci | soc2)

--min-confidence <LEVEL>
Minimum confidence to surface. Defaults to `medium` (Low-confidence findings - often noise: regex-matched-in-comments, ast-disconfirmed shapes, weak single-signal hits - are hidden by default so the scorecard reflects realistic precision). Pass `--all-confidence` (or `--min-confidence low`) to show them for debug / forensic runs; `--min-confidence high` for the strictest report. The active preset can override this default (`quick` raises it to `high`); an explicit `--min-confidence` always wins over the preset.

Philosophy: vulkro defaults trade some recall for precision so "run `vulkro scan`" yields a triage-friendly list. Tune the trade-off via this flag and `--min-evidence`. See `docs/defaults.md`.

What `high` / `medium` / `low` mean PER DETECTOR CATEGORY is documented at https://vulkro.com/docs/detection/confidence-model (per-category table). When a finding's tier surprises you, look up the category there before tuning the filter.

[possible values: high, medium, low]

--min-evidence <THRESHOLD>
Filter findings to those with cumulative evidence weight >= THRESHOLD. Only applies to findings that carry evidence - findings emitted by rules that haven't been retrofitted to emit evidence yet (empty bag) bypass the filter, so a non-zero threshold won't silently hide every legacy finding. Range: `0.0..=1.0`. Default behaviour (flag omitted) is the same as `--min-evidence 0`: nothing filtered

--all-confidence
Show every finding regardless of confidence - equivalent to `--min-confidence low`. Restores the earlier unfiltered behaviour (every finding shown) for debug or forensic runs

--strict-confidence
Filter High-confidence findings whose `evidence` bag is empty. Off by default - behaviour-preserving for callers who don't opt in. Intended for CI gates that want maximal precision: every surviving High has evidence proof per the confidence rubric (see `docs/confidence-rubric.md`). Equivalent to `VULKRO_STRICT_CONFIDENCE=1`. The `--strict-confidence` flag wins if both are set (env var is overridden to false only when its value parses as not-truthy via `env_flag_set`)

--ai-pr
Calibrate the scan for AI-generated PR diffs. Off by default. When set, vulkro inspects the latest commit against `HEAD~1..HEAD` to compute four shape signals (large diff > 200 added+removed lines, new non-test files added without a paired test file, repetitive doc-comment blocks across consecutive functions, and AI marker phrases in the commit subject + body). When at least two of the four signals fire, vulkro:

* bumps access-control findings (BOLA / mass-assignment / BFLA / broken authentication / CSRF) up one severity tier (Low -> Medium, Medium -> High, High -> Critical), * attaches an `ai-pr` evidence tag to each bumped finding so downstream tools and reports can distinguish them, * drops Low-confidence findings from the displayed report (the AI-PR mode runs a tighter confidence floor than the default scan).

Soft-fails silently if git is unavailable or the repo has fewer than two commits: the calibration sits out, the scan continues as if the flag was not set. Use this flag in CI lanes that scan diffs generated by Claude Code, Cursor agent, Copilot agent, or any coding-tool workflow where the human reviewer wants the access-control surface scrutinised more aggressively than the default.

--ai-code-segregation
Emit an AI-code segregation report alongside the normal scan output. The report breaks down findings by AI tool fingerprint (Claude / Copilot / Cursor / Aider / ChatGPT / generic AI-generated markers) and shows the fraction of findings that landed on AI-touched files. Useful for regulated-industry audits (HIPAA / PCI-DSS / FedRAMP) that need to demonstrate AI-code review hygiene. Rendered as markdown to stderr after the scan completes; the main output stream is unchanged

--attest-reviewed
Attest that the AI-touched code in this scan has been human-reviewed. Appends a `human-reviewed-ai-code` evidence row to every finding on an AI-touched file, stamped with the reviewer name (defaults to `$USER`). Lets a downstream compliance emit show "every AI-touched finding has a human-review sign-off" without per-finding manual tagging

--reviewer <NAME>
Reviewer name to embed in the `--attest-reviewed` evidence rows. Defaults to `$USER` from the environment when set, otherwise `"unknown"`. Ignored when `--attest-reviewed` is not set

--bruteforce-sinks
Run the bruteforce-sinks pass: statically drive every discovered critical-surface call site against an adversarial payload corpus and emit a finding per payload that reaches the sink without a recognized guard.

Wave 1 covers 8 sink categories (SQL, shell, HTTP, payment, LLM, file-write, deserialization, email) across Python and JavaScript / TypeScript. Fully offline; no network calls. Default confidence floor is `High`. Off by default.

--diff-only <REF>
Restrict the scan to files that differ vs the given git ref. The PR-speed mode: a 1000-file project where the PR changes 6 files scans 6 files instead of 1000. Composes with `--gate-vs` to also filter the resulting findings to the changed line range. Unset = scan everything

--bruteforce-categories <CSV>
CSV allowlist of sink categories to scan when `--bruteforce-sinks` is set. Example: `--bruteforce-categories sql,payment,llm`. Unset means "all 8 categories". Tags accepted: sql, shell, http, payment, llm, file-write, deserialization, email (plus short aliases - see docs)

--bruteforce-confidence <LEVEL>
Confidence floor for emit. Defaults to `high`; pass `medium` to surface Hi + Med findings. `low` is bench-only today. Ignored when `--bruteforce-sinks` is not set

[default: high]

--bruteforce-payload-classes <CSV>
CSV allowlist of payload super-classes to drive. Example: `--bruteforce-payload-classes injection,path-traversal`. Tags: type-confusion, numeric-extreme, size-extreme, unicode, injection, path-traversal, prompt-injection, payment-extreme

--gate
Apply the [quality_gate] section of vulkro.toml. Exit non-zero when any threshold is breached. Replaces the default "fail on any critical/high" exit policy

--validate-secrets
Validate hardcoded credentials against the live provider API. OFF by default - Vulkro stays offline-first. When enabled, each detected secret is tagged `[live]`, `[dormant]`, or `[unknown]` based on a no-op authenticated HTTP call

--gate-vs <REF>
Restrict findings to lines that changed vs the given git ref (e.g. `main`, `origin/main`, a SHA). Findings outside the changed-line set are still printed in JSON for completeness but the exit code and the table summary reflect only diff-scoped findings - this is the "block PRs that introduce new issues without flagging the rest of the repo" lane

--scope <SCOPE>
Filter the displayed findings by file kind. `all` (default) shows every finding (Dockerfiles, lockfiles, IaC manifests, docs, templates). `src` drops findings from non-source-code files so app developers see only in-app issues. The active preset can override this default (`quick` raises it to `src`); an explicit `--scope` always wins.

Trade-offs in `src` mode: * `.env*` files are kept (secrets in env files are real source-code-class bugs). * `*.yaml` / `*.yml` under `/k8s/` or `/kubernetes/` (and `kustomization.yaml`) are kept (k8s manifests are technically IaC but most users want them when narrowing to their app). * Other YAML, Terraform, Dockerfiles, lockfiles, docs (`.md`, `.rst`), `.sql`, and `.html` under `views/` or `templates/` are dropped.

Possible values:
- all: Show every finding (the default)
- src: Drop findings from non-source files: Dockerfiles, lockfiles, IaC manifests, package manifests, docs, vendored templates and SQL. Keeps `.env*` (secrets matter) and `*/k8s/*.yaml` (cluster manifests users typically want when narrowing to "their code")

--disposition <TIER>
Narrow the findings table to one disposition tier: `admitted` (proven, we stand behind it), `demoted` (real but heuristic-only, the noise floor), or `not-examined` (a structural / authz-semantic class we cannot decide offline). This is a DISPLAY filter for the table only: it changes nothing about which findings exist, the exit code, or the JSON / SARIF output (each finding there already carries its `disposition`, so machine consumers filter on that field). The summary line still reports the full `N found -> M admitted (K demoted, J not-examined)` breakdown above the narrowed table. Omit the flag to show every tier (the default)

Possible values:
- admitted: Proven tier: machine-checkable proof (a rendered taint trace, a value-verified secret, an advisory-proven SCA hit). "We stand behind it."
- demoted: Noise floor: a real finding carrying only a heuristic / shape match, no renderable proof
- not-examined: A structural / authz-semantic / absence class Vulkro flags but cannot decide offline (IDOR / BOLA, function-level authz, absence rules)

--no-cache
Bypass the per-file extraction cache (`~/.vulkro/scans.db`). Forces every source file to be re-read and re-extracted, and suppresses cache writes. Equivalent to `VULKRO_DISABLE_CACHE=1`

--reachable-only
Drop dependency / CVE findings whose vulnerable symbol isn't reachable from any first-party source file. Off by default - symbol matching is regex-based and can miss dynamic dispatch / reflection / import aliases, so we'd rather keep the finding (tagged `[unreachable]`) than silently drop it. Turn this on when you've validated the signal is right for your codebase and want a quieter report

--include-unreachable
Include findings in files unreachable from any entry point. By default these are hidden - most are noise in dead code or vendored deps. Use this to include them for triage

--fail-on <SEVERITIES>
Comma-separated severities that should make the scan exit non-zero when present (e.g. `--fail-on critical,high`). Valid names: critical, high, medium, low, info. Independent of `--gate`: when `--gate` is set, the vulkro.toml [quality_gate] thresholds win and `--fail-on` is ignored

--min-severity <SEV>
Drop findings BELOW this severity from the output (a display floor, not a gate). One of: critical, high, medium, low, info. This is the CLI equivalent of the editor's `severityThreshold` setting: run CI with the same value to see exactly the set your editor shows. Composes with `--fail-on`, which then evaluates the filtered set

--ratchet
Auto-discover `.vulkro-baseline.json` in the repo root and exit non-zero only when NEW findings appear vs the baseline. If no baseline file exists, this flag is a no-op (a warning is printed and the scan falls back to the default critical/high exit policy); use `--ratchet-strict` to require the file. An explicit `--baseline PATH` overrides the auto-discovered location

--ratchet-strict
Strict variant of `--ratchet` for CI gates: requires the baseline file to exist (auto-discovered or via `--baseline PATH`). Exits with code 2 and a clear error when missing. Otherwise behaves identically to `--ratchet`

--force-app
Bypass the framework self-scan heuristic. By default, a repo whose manifest names it as a known framework (`name: "express"`, `name = "Flask"`, etc.) is treated as framework source - endpoint extraction is skipped because every "finding" would be the framework's own internals, not the user's code. Pass `--force-app` to run the full pipeline regardless

--force-all-rules
Bypass the posture-based rule-applicability gate. Every rule runs regardless of the project's detected `ProjectPosture`. Default `false`: CSRF on JWT APIs, session-fixation on static sites, and similar inapplicable rules are silenced

--no-reachability-filter
Skip the default-on SCA reachability filter. Vulkro normally downgrades dep findings whose vulnerable symbol is not reachable from any first-party code by one severity tier (Critical → High → Medium → Low → Info, never disappearing the finding). Set this flag to restore the raw severity (useful for compliance audits that want every dep CVE at its nominal severity regardless of exploitability)

--triage
Run the offline false-positive triage pass: demote findings in test / fixture / generated / vendored / migration paths one confidence tier. Default off, fully local (no network, no model), deterministic. Pair with `--min-confidence high` to push that noise out of the default view

--pq-audit
Run the post-quantum crypto audit pack. Flags classical RSA / ECDSA / ECDH / classical Diffie-Hellman usage that NIST PQC migration guidance targets for replacement (FIPS 203 ML-KEM, FIPS 204 ML-DSA, FIPS 205 SLH-DSA). Symmetric primitives (AES, ChaCha20, SHA-2 / 3) are deliberately NOT flagged: Grover only halves effective key length, so AES-256 stays 128-bit-strong against a CRQC. Default `false`: PQ findings are a planning surface, not an exploit-today defect, so they're opt-in to keep the default scorecard focused on actionable risk

--include-large
Scan files that three per-file size caps otherwise drop before any check runs: over 1 MiB, over 5000 lines, or holding a single line longer than 5000 characters. Off by default - what the caps drop is almost always a minified bundle or a generated artefact whose regex/AST passes burn CPU without producing useful findings. A scan that does drop a file always says so, names it, and does not report a perfect health score over the tree. Pattern-based filters (vendored, generated, build output) are unaffected by this flag

--since <REF>
Incremental scan: only re-run extraction + security passes on files that changed vs `<ref>` (e.g. `main`, `origin/main`, a SHA). Findings for the unchanged files are merged in from the baseline JSON (auto-discovered as `.vulkro-baseline.json`, or the path passed via `--baseline`). Designed for CI: a PR-time scan of a 5-file diff runs in seconds instead of minutes.

Falls back to a full scan with a warning when no baseline is available - incremental output is only honest when there's a cached set of findings for the un-rescanned files.

--jobs <N>
Number of worker threads for the parallel passes (per-file extraction, per-rule dispatch). Defaults to one thread per logical CPU. Pass `1` for serial execution - useful for debugging non-determinism or memory-bounded environments. Larger values past CPU count don't add throughput.

If the thread pool was already initialised earlier in the run, Vulkro logs a warning and uses the auto-detected size.

--post-to <INTEGRATION[=CONFIG]>
Post a privacy-safe scan summary to a chat / incident / ticketing surface after the scan completes. Four integrations:

slack=<webhook-url> Slack incoming webhook (or `VULKRO_SLACK_WEBHOOK`). teams=<webhook-url> MS Teams Adaptive Card webhook (or `VULKRO_TEAMS_WEBHOOK`). jira=<PROJECT_KEY> Jira Cloud issue create (`VULKRO_JIRA_BASE_URL`, `VULKRO_JIRA_EMAIL`, `VULKRO_JIRA_TOKEN`). One issue per Critical finding. pagerduty PagerDuty Events API v2 (`VULKRO_PD_ROUTING_KEY`). Triggers ONE incident when at least one Critical is present.

Privacy contract: payloads contain only severity counts, kebab-case signal IDs, workspace-relative file paths, line numbers, and short messages truncated at 200 chars. No absolute paths, no code snippets, no vulkro.com URLs.

Failure: a transport-level failure is logged to stderr but NEVER changes the scan exit code.

Retries: each integration retries on transient failures with exponential backoff. `VULKRO_NOTIFY_RETRIES=N` (default 3, clamped to [1, 5]) tunes the budget. `VULKRO_DEBUG=1` keeps full webhook URLs in error messages instead of redacting.

--top <N>
Count for the "Fix these first" ranked list printed after the normal output. Shown by default (top 10) on the human `table` format at an interactive terminal; pass `--top N` to change the count or `--top 0` to suppress it. The rank is a deterministic synthesis of signals the scan already computed (severity, exploitability, reachability verdict, confidence, and for dependency findings the CISA-KEV / EPSS priority tags): no network, no AI. Every machine format instead carries `risk_score` and `risk_rank` on each finding (JSON). See `vulkro explain --risk-model` for the formula

[aliases: --top-risks]

--max-bundle-age <HOURS>
Advisory freshness check for the local CVE bundle. When the newest `~/.vulkro/data/cves/*.json` snapshot is older than HOURS, print a WARN telling you to run `vulkro update`. This is ADVISORY ONLY: it never changes the exit code on its own (pair it with `--max-bundle-age-fail` for a hard failure). With no local bundle present, the check warns that freshness could not be verified

--max-bundle-age-fail
Turn a stale CVE bundle into a hard error (exit 2) instead of an advisory WARN. For CI that must not run against a stale bundle. Requires `--max-bundle-age <HOURS>` to define "stale"; a missing local bundle also fails under this flag

-h, --help
Print help (see a summary with '-h')