vulkro mcp-audit
Audit MCP host configs for supply-chain and credential-handling risks.
MCP host configs (Claude Desktop, Cursor, Windsurf, VS Code, Cline,
Continue, Gemini, project-local .mcp.json) describe which local
processes the user's LLM client will launch on demand and what env /
command-line arguments they receive. A bad entry here lands attacker
code inside the shell the model already has reach into; this
subcommand surfaces the six classes of risk that show up in real host
configs.
Usage
vulkro mcp-audit [PATH] [FLAGS]
Arguments
| Argument | Description | Default |
|---|---|---|
PATH | A specific MCP config file, or a project root. When omitted, scans project-local configs in the current directory plus the well-known host-config locations. | (omitted) |
Flags
| Flag | Description | Default |
|---|---|---|
--format, -f <FMT> | table, json, ndjson, sarif, gh-pr, junit, csv. | table |
--require-mcp | Treat "no MCP configs found" as an error (exit 2) instead of a clean exit. Useful in CI jobs that expect a config to be present. | false |
--include-host | Also scan the well-known host-config locations when <path> is given. When <path> is omitted, host configs are already in scope; this flag only matters with an explicit path. | false |
--fail-on <SEVERITIES> | Comma-separated severities that should produce a non-zero exit. Same shape as vulkro scan --fail-on. | critical,high |
Exit codes
| Code | Meaning |
|---|---|
0 | No findings, or --require-mcp not set and no configs found. |
1 | Findings at or above the --fail-on threshold. |
2 | Error: malformed JSON, IO failure, or --require-mcp set with no configs found. |
Discovery paths
When <path> is omitted, the auditor walks the following well-known
locations and reads whichever exist (missing files are skipped
silently):
~/Library/Application Support/Claude/claude_desktop_config.json(macOS)~/.config/Claude/claude_desktop_config.json(Linux)%APPDATA%\Claude\claude_desktop_config.json(Windows, documented for completeness)~/.cursor/mcp.json,~/.cursor/mcp_config.json~/.windsurf/mcp.json,~/.codeium/windsurf/mcp_config.json~/.vscode/mcp_settings.json,~/.vscode-server/mcp_settings.json~/.cline/cline_mcp_settings.json~/.gemini/settings.json~/.continue/mcp.json- Project-local under the current directory:
./.mcp.jsonand./mcp.json
A missing path is normal: an absent ~/.cursor/mcp.json just means
Cursor is not configured on this machine.
Rules
The auditor emits stable finding IDs in the MCP-NNN family. Every
rule page links back here.
| ID | Rule | Severity |
|---|---|---|
| MCP-001 | Unpinned npx / uvx registry install. The package re-resolves at every host launch. | Medium (High when filesystem-scope) |
| MCP-002 | Git install pointed at a mutable ref (HEAD, main, no ref). A force-push silently changes what the host runs. | High |
| MCP-003 | Filesystem server mounted at /, $HOME, or a shallow home subdir. Broad model reach turns one prompt-injection into a wide blast radius. | High (root) / Medium (shallow) |
| MCP-004 | Credential literal inlined in an env block instead of ${VAR}. Provider-format hit elevates to Critical. | Critical / High / Medium |
| MCP-005 | Cleartext http:// endpoint, or HTTPS endpoint with no visible auth credential. | High (http) / Medium (https without auth) |
| MCP-006 | Pinned MCP server version matches the Vulkro compromised-release catalog. | Critical |
Inventory envelope
--format json emits both the rule findings and a discovery inventory
so the consumer can distinguish "nothing to scan" from "scanned N
configs, all clean":
{
"findings": [ /* SecurityFinding records */ ],
"mcp_inventory": {
"paths_checked": [
"/Users/me/.cursor/mcp.json",
"/Users/me/.windsurf/mcp.json",
"/Users/me/work/project/.mcp.json"
],
"paths_with_configs": [
"/Users/me/.cursor/mcp.json",
"/Users/me/work/project/.mcp.json"
],
"servers": [
{
"name": "filesystem",
"source": "/Users/me/.cursor/mcp.json",
"host_kind": "cursor",
"command": "npx",
"transport": "stdio"
}
]
}
}
host_kind is the human-readable host classification
(claude_desktop, cursor, windsurf, vscode, cline, gemini,
continue, project-local, or unknown). transport is one of
stdio, http, sse, or unknown, derived from the entry's type
field or the presence of command vs. url.
Examples
# Default: walk every well-known host config plus project-local.
vulkro mcp-audit
# Audit a single host config explicitly.
vulkro mcp-audit ~/.cursor/mcp.json
# Project-local + host configs together, with explicit path.
vulkro mcp-audit . --include-host
# CI gate: require an MCP config to exist and fail on critical or high.
vulkro mcp-audit --require-mcp --fail-on critical,high
# Stream NDJSON into jq for ad-hoc filtering.
vulkro mcp-audit --format ndjson | jq 'select(.severity == "critical")'
Related
vulkro scan- the broader pipeline; SUP-COMPROMISE-* findings on lockfiles live there.vulkro extension-audit- same shape, but for installed editor and browser extensions.vulkro respond- "is THIS advisory or package in my project?" in under a second.vulkro explain MCP-001(and the rest) renders per-rule rationale even when no live finding hits.- Confidence model - how
High,Medium, andLoware calibrated for MCP findings.
Command reference
Generated from vulkro help mcp-audit 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.
Check AI-assistant (MCP) configuration files for risky servers and leaked credentials.
Audit MCP host configs (Claude Desktop, Cursor, Windsurf, VS Code, Cline, Continue, Gemini) plus project-local `.mcp.json` for supply-chain and credential-handling risks.
Rules: MCP-001..006. See `vulkro explain MCP-001` (etc.) for the rationale and remediation per rule.
Path discovery: * If `<path>` is given and exists as a file, scan that file only. * If `<path>` is given and is a directory, scan its `.mcp.json` and `mcp.json` (project-local). With `--include-host` the well-known host-config locations are also scanned. * If `<path>` is omitted, scan project-local under the current directory AND the well-known host-config locations: - `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) - `~/.config/Claude/claude_desktop_config.json` (Linux) - `%APPDATA%\Claude\claude_desktop_config.json` (Windows - documented for completeness; this CLI is macOS/Linux focused) - `~/.cursor/mcp.json`, `~/.cursor/mcp_config.json` - `~/.windsurf/mcp.json`, `~/.codeium/windsurf/mcp_config.json` - `~/.vscode/mcp_settings.json`, `~/.vscode-server/mcp_settings.json` - `~/.cline/cline_mcp_settings.json` - `~/.gemini/settings.json` - `~/.continue/mcp.json`
Exit codes: `0` no findings (or `--require-mcp` not set and no configs found). `1` findings at or above `--fail-on` threshold. `2` error: malformed JSON, IO failure, or `--require-mcp` set with no configs found.
Usage: vulkro mcp-audit [OPTIONS] [PATH]
Arguments:
[PATH]
Path to a specific MCP config file or to a project root. When omitted, the project-local CWD configs are scanned alongside the well-known host-config locations
Options:
-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]
--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
--require-mcp
Treat "no MCP configs found" as an error (exit 2) instead of a success (exit 0). Useful for CI jobs that should fail when the expected config isn't present. Off by default
--include-host
Also scan the well-known host-config locations when `<path>` is given. Default: off when `<path>` is explicit (keep the audit scoped); on when `<path>` is omitted
--fail-on <SEVERITIES>
Severities that should produce a non-zero exit (comma list). Default `critical,high`. Same shape as `vulkro scan --fail-on`
[default: critical,high]
-h, --help
Print help (see a summary with '-h')