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.