mcp-audit GitHub

Security and quality

A scanner you run on your own machine has to earn that access. This page says what mcp-audit does and does not do, how it is tested and released, and what we have measured, with a link to the evidence for each claim.

To report a security issue in mcp-audit itself, follow the security policy.

At a glance

What a default scan does

It finds MCP configs, Claude Code settings and installed plugins (and agent instruction files when you add --include-agent-files), reads them, and reports what it found. It does not start servers, run scripts or call git. It collects no telemetry and makes no network calls. It keeps a hash of each server's config entry in your user config folder, so the next scan can tell what changed.

Anything that uses the network is a flag you add:

  • --connect starts or connects to each configured server and reads its live tool list.
  • --check-vulns looks up dependency advisories at deps.dev and OSV.dev.
  • --verify-hashes downloads package tarballs to check their hashes.
  • --verify-signatures fetches Sigstore provenance from npm and PyPI.
  • vet --online fetches the verdict from this site; lock --resolve asks the package registry what a version range resolves to; update-registry downloads the latest registry from GitHub.

--offline refuses every network flag. One more thing is opt-in: if you run mcp-audit register, later check runs send your mcp-audit version and grade. Registration never sends anything about your configs.

Evidence: docs/telemetry.md, docs/privacy.md, src/mcp_audit/_network.py

Tests and CI

mcp-audit has 3,818 automated tests. The suite runs on Linux, macOS and Windows with Python 3.11 and 3.12 on every push and pull request, in a workflow that also runs:

  • Lint and format checks.
  • A run with every optional extra installed.
  • A check that every finding ID has an OWASP mapping.
  • A check that the counts in the README match the code.
  • A check that the built wheel's metadata is accepted by PyPI tooling.

GitHub CodeQL also analyses the code on pull requests and weekly.

6 test classes in tests/test_exploit_validation.py rebuild published attack proofs of concept and assert the exact finding IDs and severities they produce.

Evidence: .github/workflows/ci.yml, scripts/update_test_count.py

Releases you can verify

Releases are published to PyPI from GitHub Actions with Trusted Publishing, so publishing needs no stored PyPI token; each upload carries PEP 740 attestations that tie it to the workflow that built it. Standalone binaries are built for macOS (Apple silicon), macOS (Intel), Linux (x86-64) and Windows (x86-64), and each one ships with a CycloneDX SBOM. A release gate checks that the tag, the version in pyproject.toml and a non-empty changelog section all agree before anything is published.

Evidence: .github/workflows/release.yml, docs/building-binaries.md

A signed advisory feed

OSV and NVD track vulnerable packages, but no feed tracks MCP-specific findings, so mcp-audit publishes one. mcp-audit advise turns findings into OSV 1.6.0 records, canonicalized with RFC 8785 so the same finding produces the same bytes on any machine. A weekly build signs the feed with the project's minisign key, and mcp-audit feed verify checks every record against the signed index offline. A separate daily check fails loudly if the published feed gets close to expiry.

Published feed: https://raw.githubusercontent.com/adudley78/mcp-audit/feed/index.json

Project public key B80A5199FE49D0E1:

RWTh0En+mVEKuP0KTB1CfAp1xQsJ3X2yplfbWzXZnHx9MRyuZ/z0Otz5

Evidence: docs/advisory-feed.md, keys/mcp-audit-feed.pub

Mapped to the OWASP MCP Top 10

121 of the 126 finding IDs carry OWASP MCP Top 10 codes; the other 5 record bookkeeping or errors, not a risk. Together the checks cover 10 of the 10 categories. CI fails a pull request that adds a finding ID without a mapping. SARIF output includes the taxonomy, so the codes show up in GitHub code scanning.

  • MCP01 Token Mismanagement and Secret Exposure 24 checks
  • MCP02 Privilege Escalation via Scope Creep 12 checks
  • MCP03 Tool Poisoning 24 checks
  • MCP04 Software Supply Chain Attacks 35 checks
  • MCP05 Command Injection and Execution 26 checks
  • MCP06 Intent Flow Subversion 13 checks
  • MCP07 Insufficient Authentication and Authorization 12 checks
  • MCP08 Lack of Audit and Telemetry 1 check
  • MCP09 Shadow MCP Servers 13 checks
  • MCP10 Context Injection and Over-sharing 10 checks

See which checks map to each category

Evidence: docs/owasp-mapping.json, scripts/validate_owasp_mapping.py

Measured in public

On 2026-10-22, we scanned the 100 most-downloaded MCP servers on npm with mcp-audit and published the corpus, the scripts and every aggregate number.

  • 60 of the 100 servers completed a live connection; together they exposed 1,611 tools.
  • mcp-audit's poisoning rules raised 661 findings, and none of them was a real attack; 488 came from one rule, POISON-060.
  • Dependency lookups found 92 distinct advisories across 27 servers' dependency trees; mcp-audit does not check whether the vulnerable code is reachable.

The scan also measured mcp-audit. The published write-up lists these defects in mcp-audit itself:

  • POISON-060 (Unicode homoglyph substitution) fires on full-width punctuation in tool descriptions written in Chinese.
  • SC-004 compares versions numerically and reads @modelcontextprotocol/server-filesystem 2.0.0 as older than 2025.7.1 after the package moved from calendar versions to semver, so it reports two fixed CVEs as present.
  • COMM-010 ("npx without a pinned version") fires on every server in the config, all of which are pinned, because it checks the command and not the arguments.
  • SC-003 reports @netlify/mcp as a lookalike of @mintlify/mcp.
  • --check-vulns reports every dependency advisory as INFO with no summary, HIGH advisories included.

Evidence: docs/research/2026-10-top100-npm/

Where the detection logic comes from

Every pattern is written for mcp-audit from published research; no code is copied from other scanners. Each finding's source is cited: vendor research, CVEs, academic measurements and framework guidance, listed per rule.

Evidence: PROVENANCE.md, docs/severity-framework.md

What it does not catch

A scanner is only as useful as its known limits. We keep them in one public file, including detections we measured and chose not to ship. A few that matter most:

  • A static scan sees what a config says. What a remote server actually serves is only visible with --connect, which starts the servers.
  • Regex-based poisoning detection can be evaded and can misfire; the measurement above shows where it misfired.
  • Dependency advisories are reported without reachability analysis.
  • Some agent surfaces (for example Windsurf, Augment and Kiro instruction files) are not read yet.

Evidence: GAPS.md

A curated registry

The registry behind vet and the server pages lists 55 packages, added by review rather than crawling. A package is marked verified only with evidence that the submitter controls it; 26 are verified today. A weekly job checks OSV for new advisories on every listed package.

Evidence: registry/known-servers.json, docs/registry-contributions.md

License

Apache 2.0, with every feature available to every user. There are no license keys, paid tiers or gated commands; the licensing code that once existed was removed, and the project's own rules forbid bringing feature gating back.

Evidence: LICENSE, CLAUDE.md