vulkro-sf scan
The primary scanning command. Walks an SFDX project (or a retrieved metadata folder), runs every Vulkro Salesforce detector over the source, and emits findings in the requested format. The analysis runs entirely on the local machine: there is no content telemetry and no upload, and your source, metadata, and findings never leave the box.
Live-org posture, identity, Connected Apps, and Agentforce actions are
not pulled by scan itself. Reach those through the dedicated
vulkro-sf org subcommands, which talk to
the live org via your sf CLI login.
Synopsis
vulkro-sf scan [PATH] [flags]
PATH defaults to the current directory. The directory should contain
sfdx-project.json (SFDX) or package.xml (legacy MDAPI). Use
--metadata-only to scan a bare retrieved-metadata folder that has
neither.
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--format <fmt> | enum | table | Output format. Reports: table (human-readable summary on the terminal), json (full machine-readable findings), sarif (the SARIF 2.1.0 format consumed by GitHub Code Scanning and most SAST dashboards). PR / MR decoration, meant to be piped into your SCM (for example | gh pr comment -F -): github-annotations, gh-pr-inline, gh-pr, gitlab-mr, bitbucket-pr, azure-pr. The exit code is the same for every format: 0 = no findings, 1 = findings, 2 = error. |
--metadata-only | bool | off | Skip SFDX project detection; treat PATH as a retrieved metadata folder. Use this when scanning a folder that holds only metadata (no Apex source, no sfdx-project.json), for example a console-only org snapshot. |
--ai-explain | bool | off | Advisory AI overlay: after the deterministic scan finishes, ask the local AI layer to explain, in plain Salesforce language, why the candidate findings matter for an AppExchange Security Review. Opt-in and additive: the deterministic findings, their severities, the SARIF output, the --gate-vs gate, and the exit code are unchanged. See AI advisory overlay below. |
--ai-model <MODEL> | string | resolved | Model tag override for --ai-explain. Highest precedence: the flag beats the VULKRO_AI_MODEL env var, which beats the saved vulkro-sf ai use selection, which beats the built-in default (qwen2.5-coder:7b). |
--ai-url <URL> | string | resolved | AI runtime base URL override for --ai-explain (any OpenAI-compatible endpoint). Same precedence chain as --ai-model, with VULKRO_AI_URL as the env var and http://127.0.0.1:11434/v1 (local Ollama) as the default. A loopback URL is allowed under VULKRO_OFFLINE; a cloud URL is refused offline. |
--no-ai | bool | off | Hard off-switch for the AI layer on this run, even if --ai-explain is passed. The scan is deterministic only. |
--ai-code-segregation | bool | off | Emit an AI-code segregation report alongside the normal scan output: which findings landed on AI-authored files, broken down per tool. Marker-based only: a file counts as AI-authored when a comment or header carries an explicit tool marker (Claude, GitHub Copilot, Cursor, Aider, ChatGPT, or a generic "AI-generated" annotation). Code style is never guessed. Covers Apex classes and triggers plus LWC and Aura component JavaScript. Rendered as markdown to stderr after the scan; stdout and the exit code are unchanged. Ignored with --gate-vs or --metadata-only (a stderr note says so). This report is deterministic; it does not call a model. |
AI advisory overlay (--ai-explain)
--ai-explain adds a second-opinion narrative on top of a finished
deterministic scan. The invariant is strict: AI assistance is advisory
and runs against a local model by default. It never changes a finding,
a severity, an output format's deterministic content, or the exit code,
and the published benchmark is AI-free. If the AI layer is off,
misconfigured, or unreachable, the overlay is skipped with a stderr
note and the scan completes normally.
What it does, per finding that qualifies as a candidate (core authorization intent classes, mandatory Security Review signals such as SOQL injection, CRUD / FLS, with / without sharing, and Flow system-mode, plus findings whose reachability is unknown): the resolved model writes a plain-language explanation of why the flagged pattern matters for an AppExchange Security Review. Every advisory is labelled non-deterministic and excluded from the benchmark.
How the advisory is carried, by format:
--format json: each annotated finding gains an additiveai_advisoryobject (explanation text, provenancemodelandprovider, and explicitadvisory/non_deterministic/excluded_from_benchmarkmarkers). Findings without an advisory are byte-identical to a run without the flag.--format table: the narratives print to stderr, so a captured stdout stays byte-identical with the flag off.- Every other format (SARIF and the PR-decoration formats) stays fully deterministic: the flag is ignored there with a stderr note.
The overlay runs only on the default full-engine path. With --gate-vs
or --metadata-only the flag is ignored and a stderr note says so:
those lanes stay deterministic.
Setup and model resolution: the overlay uses the shared local AI layer.
Run vulkro-sf ai setup once to install or point at a local runtime,
pick a model, and self-test it; the selection is stored in
~/.vulkro/scans.db and is shared with the core vulkro binary.
Without a saved selection the scan falls back to the default model,
qwen2.5-coder:7b (Apache-2.0), on the default local Ollama endpoint
at http://127.0.0.1:11434/v1 (on a machine with fewer than 4 physical
cores the default falls back to phi4-mini:3.8b, MIT). Per-run
overrides: --ai-model and --ai-url win over the VULKRO_AI_MODEL
and VULKRO_AI_URL env vars, which win over the saved selection.
VULKRO_AI_KEY supplies the bearer token for a cloud endpoint only and
is never written to disk.
Air-gap behaviour: a loopback (local) endpoint is allowed even under
VULKRO_OFFLINE, because nothing leaves the machine. A cloud endpoint
is refused offline.
Examples
# Basic source scan over an SFDX project. Prints a one-line table summary.
vulkro-sf scan .
# Full JSON output, piped to a file.
vulkro-sf scan . --format json > vulkro-sf.json
# CI-friendly SARIF for GitHub Code Scanning upload.
vulkro-sf scan . --format sarif > vulkro-sf.sarif
# Metadata-only folder (no Apex, no sfdx-project.json - just a retrieved
# org snapshot).
vulkro-sf scan ./retrieved-metadata --metadata-only
# Deterministic scan plus the advisory AI overlay. The JSON on stdout is
# the normal deterministic report; annotated findings carry an additive
# ai_advisory object. Severities and the exit code are unchanged.
vulkro-sf scan . --format json --ai-explain > vulkro-sf.json
# Same overlay on the table format: narratives go to stderr, stdout is
# byte-identical to a run without the flag.
vulkro-sf scan . --ai-explain
# Pin the model for this run only (flag beats env beats saved selection).
vulkro-sf scan . --ai-explain --ai-model qwen2.5-coder:7b
# CI guard: --no-ai is a hard off-switch, so the run stays deterministic
# even when a wrapper script adds --ai-explain.
vulkro-sf scan . --ai-explain --no-ai
Exit codes
0- scan completed, no findings.1- scan completed, findings were reported (or the install is on a Pro tier that has hit its hard-block boundary).2- error: bad arguments, IO failure, parse error, or internal crash. The error message names the cause and the next step.
Where to go next
- vulkro-sf antipatterns - run the Salesforce Well-Architected anti-pattern detector instead of the security pipeline.
- vulkro-sf appexchange-report - render the pre-submission readiness HTML report.
- Output: SARIF - the format reference for the CI / code-scanning lane.