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
- Offline by designA default scan makes no network calls. Every network check is a flag you add.
- Reads, never runsScanned files are parsed, never executed: no servers started, no scripts, no git.
- No telemetryNo usage data, no crash reports and no account to create.
- Signed advisory feedOSV 1.6.0 records signed with the project's minisign key, published weekly.
- Attested releasesPyPI Trusted Publishing with PEP 740 attestations, and a CycloneDX SBOM for every binary.
- 3,818 automated testsThe suite runs on Linux, macOS and Windows with Python 3.11 and 3.12 on every push and pull request.
- OWASP MCP Top 10Findings map to 10 of 10 categories, and CI checks the mapping on every change.
- Measured in publicOur false positives on the top 100 npm servers are published, and GAPS.md lists what we miss.
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:
--connectstarts or connects to each configured server and reads its live tool list.--check-vulnslooks up dependency advisories at deps.dev and OSV.dev.--verify-hashesdownloads package tarballs to check their hashes.--verify-signaturesfetches Sigstore provenance from npm and PyPI.vet --onlinefetches the verdict from this site;lock --resolveasks the package registry what a version range resolves to;update-registrydownloads 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/z0Otz5Evidence: 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.
MCP01Token Mismanagement and Secret Exposure 24 checksMCP02Privilege Escalation via Scope Creep 12 checksMCP03Tool Poisoning 24 checksMCP04Software Supply Chain Attacks 35 checksMCP05Command Injection and Execution 26 checksMCP06Intent Flow Subversion 13 checksMCP07Insufficient Authentication and Authorization 12 checksMCP08Lack of Audit and Telemetry 1 checkMCP09Shadow MCP Servers 13 checksMCP10Context 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-filesystem2.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/mcpas a lookalike of@mintlify/mcp. --check-vulnsreports 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