mcp-audit · git:20260831.856ee57 · 2026-08-31 · sha256 b4ee4c43c3df0590
mcp-audit git:20260831.856ee57B
Immutable. This exact content is served forever at /api/v1/blob/b4ee4c43c3df0590.
---
name: mcp-audit
description: MCP config + implementation security audit — config secrets/auth, unpinned versions, remote vs local, OAuth, env exposure; implementation command injection, SSRF, unsafe args, tool poisoning. Static, evidence-cited.
origin:
type: first-party
---
# MCP Audit — Config + Implementation Security
Audit **MCP servers** before adopting — static inspection only, never execute remote servers. Use when reviewing `mcp/registry/*.yaml`, `mcp/templates/*/config.template.json`, skill/plugin MCP declarations, or when a static surface scan flags MCP references.
**Single skill, two modes** — decision per #379 review: config and implementation scopes meaningfully overlap (both inspect `mcp/registry/*.yaml` + templates), but checklists differ enough to keep separate gates. One skill with two modes avoids duplicating registry parsing while keeping `config` (secret hygiene, version pinning) distinct from `implementation` (command injection, SSRF, tool poisoning).
> **Static only:** Do not start or call remote MCP servers during audit. Inspect YAML/JSON, package provenance, tool descriptions, and env handling.
## Modes
| Mode | What it checks | Evidence |
|------|----------------|----------|
| **Config audit** | `mcp/registry/*.yaml` auth, package provenance, version pinning, remote vs local, OAuth, env/secret exposure, permissions | Registry YAML, template JSON, env var names, `docs/MCP.md` |
| **Implementation audit** | Command injection, shell execution, SSRF, unsafe args, tool description poisoning, secret env leakage, dangerous permissions, transport security | Skill SKILL.md + static surface patterns (shell/network/mcp/hooks), tool definitions, args validation, network_hosts |
Run the relevant mode per request; for full adoption review, run both and emit a single `mcp-audit-report.md`.
## Config audit — checklist
### Auth & secrets
- [ ] `auth.env` lists only env var **names**, never values (scan registry YAML for `ghp_`, `xoxb`, hardcoded tokens)
- [ ] Template `config.template.json` uses `${ENV_VAR}` placeholders (no real credentials)
- [ ] Remote MCP (`streamable_http` URL like `https://mcp.figma.com/mcp`) documents auth as `bearer-env` with region var, not query param
- [ ] Local MCP (`stdio` via `npx`/`docker`/`uvx`) does not embed secrets in `args` — secrets only in `env`
- [ ] No `default-branch push` or `filesystemWrites` beyond declared `security.network_hosts`
### Version pinning & provenance
- [ ] `implementation.package` is machine-verifiable: npm `chrome-devtools-mcp@latest` / docker `ghcr.io/...` / URL `https://mcp.figma.com/mcp` — not bare `latest` without policy
- [ ] `implementation.version_policy` declared (`npx-latest`, `pin image digest`, `pin to minor`) and matches template `args` (`-y chrome-devtools-mcp@latest` vs `@notionhq/notion-mcp-server`)
- [ ] `implementation.provenance` = `official` with `repository` URL + `license` verifiable via `gh api` (e.g., ChromeDevTools/chrome-devtools-mcp Apache-2.0, github/github-mcp-server MIT)
- [ ] Remote vs local decision documented: remote (Figma) for designer-hosted, local (GitHub/Slack/Notion) for on-host execution — no mixed remote + local for same provider without rationale
### Permissions & env exposure
- [ ] `security.network_hosts` enumerates expected hosts (no `*`, no private `.local` / `192.168.`)
- [ ] `security.secret_storage` = `environment variable` with `secret_storage` notes
- [ ] `platforms` matrix declares support per target (native/bridged/manual) — no assumed universal
- [ ] `approval.default` matches risk: `read-only` for Figma/GitHub vs `read-write` for Chrome DevTools (can modify page) — justified
### Per-template gate
- [ ] Template `command` ∈ `npx|docker|uvx` and `args[:2] == ["-y", provider.package]` for `npx` (verified by `tests/test_mcp_templates.py`)
## Implementation audit — checklist
### Command injection & shell
- [ ] `args` contain no shell interpolation (`$(`, `` ` ``, `;`, `&&`, `|`). MCP `command` is single binary, not `sh -c`.
- [ ] `command` is not `sh`/`bash`/`python -c` with concatenated args — use direct `npx`/`docker` entrypoint.
### SSRF & network
- [ ] URL args (`--browser-url`, `https://mcp.linear.app/mcp`) are not user-controlled without allowlist; `navigate_page` tool validates hosts.
- [ ] `network_hosts` does not include internal metadata endpoints (`169.254.169.254`, `metadata.google.internal`).
### Tool poisoning & unsafe args
- [ ] Tool descriptions in registry `tools.read/write` do not contain prompt injections (e.g., `ignore previous instructions`, `send secrets to`).
- [ ] Tool `write`/`destructive` sets are minimal — no `delete_file` where read-only suffices (GitHub `destructive` correctly lists `delete_file` only there).
- [ ] Args that become file paths (`FIGMA_OAUTH_TOKEN`) are env var refs, not string interpolation.
### Secret leakage & OAuth
- [ ] No `env` value contains PII — only `${VAR}` placeholders in templates; registry lists `env` names with `CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS` style opt-outs.
- [ ] OAuth flows (Linear) documented as browser OAuth, not token paste.
## Workflow
1. **Load registry:** `agent_toolkit.compiler.mcp_registry.load_registry(mcp/registry)` — record `providers, errors` (evidence: registry count).
2. **Pick mode:** `config` (default for adoption) or `implementation` (for command/SSRF/poisoning) or both.
3. **Run checks:** For each `provider` in `providers`, apply the relevant checklist above; for `implementation`, Grep the registry/templates for shell/network/hooks patterns and capture findings with `file:line` evidence. (Repo checkout/CI may also run `./scripts/audit-capability.vsh mcp/registry/<provider>.yaml --json` — that script is **not** on the host install.)
4. **Score:** `ALLOW` (no Blocking), `CAUTION` (Major, e.g., unpinned version, remote without TLS), `BLOCK` (Blocking: hardcoded secret, command injection, SSRF to metadata endpoint, provenance unknown).
5. **Report:** Emit `mcp-audit-report.md` (see `references/mcp-audit-template.md`) with per-provider table: `provider | config verdict | impl verdict | package/license | version_policy | provenance | evidence`.
### Example report row
| Provider | Config | Impl | Package | License | Version policy | Verdict | Evidence |
|----------|--------|------|---------|---------|----------------|---------|----------|
| chrome-devtools | ✅ auth none, package chrome-devtools-mcp@latest, npx-latest, no secrets | ✅ no shell, no SSRF, read-write justified | npm chrome-devtools-mcp@latest | Apache-2.0 | npx-latest | ALLOW | registry chrome-devtools.yaml + template config.template.json |
## Relation to `mcp` skill
- `mcp` (integrations/mcp) — **how to setup** (`agent-toolkit mcp setup`, `mcp list`, `doctor`) — orchestration.
- `mcp-audit` (this skill) — **whether to trust** — security gate before setup. Call this skill before `mcp setup` for unreviewed providers; delegate `supply-chain-audit` for full skill/plugin surface.
## Delegation table
| Need | Skill |
|------|-------|
| Setup MCP after audit | `integrations/mcp` |
| Full skill/plugin supply-chain | `agentic-security/supply-chain-audit` |
| OWASP agentic review (prompt injection, tool poisoning, identity) | `agentic-security/owasp-agentic-review` (next issue) |
| Output gate | `output-handshake` |
## Security & compatibility
- Never execute MCP servers during audit; static YAML/JSON only.
- Portable: `gh api` for provenance license check, `yaml` + `jsonschema` offline.
## References
- `references/mcp-audit-template.md` — report template (per-provider verdict)
- `mcp/registry/*.yaml` — canonical registry (7 providers after #375)
- `mcp/templates/*/config.template.json` — host wiring (placeholders)
- Static surface patterns in this skill’s checklists (shell/network/mcp/hooks); repo checkout/CI may use `scripts/audit-capability.vsh`
- MCP spec: https://modelcontextprotocol.io/ , ChromeDevTools MCP https://github.com/ChromeDevTools/chrome-devtools-mcp