Skip to main content

Static analyzer for MCP server repos: 4 checks (BadHost / Starlette CVE-2026-48710, FastMCP wrapper-layer asyncio.run bug, loose @mcp.tool() schemas, subprocess command-injection w/ cross-function taint propagation).

Project description

mcp-audit

Static security + correctness audit for MCP server repos.

pip install mcpdone-audit     # PyPI distribution (the plain 'mcp-audit' name is squatted; PEP 541 blocks it)
mcp-audit                     # scan the current directory
mcp-audit /path/to/repo       # scan a specific repo
mcp-audit --json              # machine-readable output
mcp-audit --check fastmcp_wrapper_layer  # one check only
mcp-audit --list-checks       # list available checks

Exit codes: 0 clean, 1 at least one finding, 2 usage error.

What it checks

Check ID Severity What it finds
starlette_badhost HIGH / MED Starlette < 1.0.1 in pyproject.toml, requirements*.txt, uv.lock, poetry.lock, pdm.lock. BadHost (CVE-2026-48710) lets a crafted HTTP Host header bypass path-based authorization. Affects any HTTP/SSE-transport MCP server. Stdio servers are unaffected.
fastmcp_wrapper_layer HIGH Sync @mcp.tool() functions that call asyncio.run(...) inside their body. FastMCP invokes tools inside an already-running event loop; asyncio.run() raises RuntimeError. Looks fine in unit tests, dies on the first real protocol call.
tool_input_validation LOW @mcp.tool() parameters typed as bare str / bytes / Any / list[Any] / dict[..., Any] or with no annotation at all. The schema FastMCP exposes to the LLM is the substrate prompt-injection-via-tool-description attacks rely on; constraining it (Annotated[str, Field(max_length=N)], Literal[...], Pydantic models) closes the window without losing expressiveness. Hygiene check, not a CVE — expect findings even on well-written servers. Added in v0.2.
command_injection HIGH @mcp.tool() functions where a tool parameter (or a local tainted via assignment / .format() / string concat) flows into os.system, os.popen, or subprocess.* with shell=True or a tainted-interpolated command string. v0.4 added same-file cross-function taint propagation: the analyzer now follows local helper calls (positional + keyword binding, recursion-visited guard), so tool -> helper -> sink flows are caught. Cross-file taint remains out of scope. The list-of-args / no-shell pattern is correctly NOT flagged. Added in v0.3, cross-function in v0.4.

More checks are landing — hard-coded secrets, write-API tools missing a FORBIDDEN_NAMES-style guardrail, read-only-by-default violations, path traversal in filesystem-touching servers.

Output format

$ mcp-audit examples/bad/
[HIGH  ] starlette_badhost @ uv.lock
           uv lockfile pins starlette==0.36.3 — vulnerable to BadHost (CVE-2026-48710). Patched in 1.0.1.
           -> Upgrade Starlette to >=1.0.1 (the BadHost patch). If FastAPI pulls Starlette transitively, pin it explicitly. ...

[HIGH  ] fastmcp_wrapper_layer @ server.py:18
           tool 'fetch_url' (def) calls asyncio.run() inside its body. FastMCP invokes tools inside an already-running event loop, and asyncio.run() raises RuntimeError when nested. This will fail at the first real MCP protocol call even if every unit test passes.
           -> Convert the tool to `async def` and replace `asyncio.run(...)` with `await`. ...

mcp-audit: 2 finding(s) — 2 high

--json emits one object: {"root": "...", "finding_count": N, "findings": [...]}. Each finding has check, severity, path, line, message, remediation.

What this is not

  • It is not a runtime sandbox. Static analysis only.
  • It does not install your venv to introspect it. It reads what's declared (manifests + lockfiles + source).
  • It will not detect every vulnerability — only the classes its checks know about. Treat zero findings as "no known issues from this tool," not as a clean bill.

Background

Development

git clone https://github.com/Alienbushman/mcpdone-samples
cd mcpdone-samples/mcp-audit
pip install -e ".[dev]"
pytest
python smoke_test.py

To add a check: drop src/mcp_audit/checks/<name>.py exposing a module-level CHECK_ID and a check(root: Path) -> list[Finding] callable. Register it in src/mcp_audit/checks/__init__.py. Add fixtures + tests under tests/.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcpdone_audit-0.6.0.tar.gz (25.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcpdone_audit-0.6.0-py3-none-any.whl (22.2 kB view details)

Uploaded Python 3

File details

Details for the file mcpdone_audit-0.6.0.tar.gz.

File metadata

  • Download URL: mcpdone_audit-0.6.0.tar.gz
  • Upload date:
  • Size: 25.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mcpdone_audit-0.6.0.tar.gz
Algorithm Hash digest
SHA256 ca99e6862658e197dbc722a3c6af211a18df07e615ebc7a0afe7c7e3c61a89e4
MD5 747a3b2d24398878eae73f2268f1ef13
BLAKE2b-256 cda86b19e496f27a75cc6d471470fc9642b99ee3d0349b4a53e41abb85f7eb8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcpdone_audit-0.6.0.tar.gz:

Publisher: release.yml on Alienbushman/mcpdone-samples

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mcpdone_audit-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: mcpdone_audit-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 22.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mcpdone_audit-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7968950b32895b438f8551f99032b21cfa21542cffdddc2ca43d397981a82148
MD5 f7c308707b525ad554034c841c67ae0c
BLAKE2b-256 a212ead4c71128a3eec38b0b8b572b8bb0c31947ed7e99b7a36124975705355d

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcpdone_audit-0.6.0-py3-none-any.whl:

Publisher: release.yml on Alienbushman/mcpdone-samples

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page