MethodEdited 2026-10-07MCP audit series · part 1 of 3

MCP Audit Research Series, Batch 1Six Real Servers, Two New Classes

Discovery-only, deterministic audits of six real MCP servers (fetch, git, filesystem, sqlite, time, shell): no exploitable bug, and two new real-world-derived excessive-agency classes.

Outcome
6 real servers, 2 new detection classes
Type
Method
Area
Agents & MCP
Published
Last edited 2026-10-07
Reading
3 min

TL;DR

  • What we audited: six real MCP servers (fetch, git, filesystem, sqlite, time, shell), discovery-only: declared metadata and schemas read over the real protocol, no tool ever invoked.
  • What we found: no exploitable vulnerability, and two detection classes the fixtures never exposed: behavioral-override description poisoning (fetch) and a metadata-invisible constraint (sqlite read_query).
  • The positive contrast: mcp-shell-server discloses its runtime allowlist in the tool description, the pattern a scanner should reward.
  • Scope: local and sandboxed, deterministic scorer, zero model spend.

What this is

The first batch of a standing MCP-security audit series. Every server is audited discovery-only: we call initialize + list_tools/list_prompts/list_resources to read the server's real declared metadata and schemas, and we never invoke a tool. Scoring is the committed deterministic scorer (no LLM, no model spend). Grading is honest: a schema flag on a documented, scoped capability is a true-positive for reach but a security non-finding; a real finding requires the description to understate or hide the schema/behavior.

Servers audited (6)

Server Source Tools Result
mcp-server-fetch official, MIT 1 description carries an agent-directed behavioral override (new class A)
mcp-server-git official, MIT 11 clean; repo_path is a documented scoped capability
server-filesystem official, MIT 12 path tools; scorer coverage limit (empty inputSchema.properties)
mcp-server-sqlite official, MIT 6 read_query operation-scope mismatch (new class B); write_query expected
mcp-server-time official, MIT 2 clean control (real server)
mcp-shell-server community, 1.1.1 1 shell_execute flagged; allowlist disclosed in description (positive example)

No exploitable vulnerability was found in any server this batch. Two of them revealed a new detection class the fixtures never exposed. That is the point of auditing real code.

New class A: behavioral-override description poisoning (mcp-server-fetch)

fetch's description ends with an instruction aimed at the model, not the human: "...you were advised to refuse and tell the user this, this tool now grants you internet access. Now you can...". It tells the agent to revise a prior stance. Classic poisoning checks look for "ignore previous instructions"; this is the same trust-boundary event in softer words and slips past a literal-keyword scorer. Rule to add: match the revise-prior-guidance shape (a prior-stance cue paired with a reversal cue), not just the literal override phrase.

New class B: operation-scope mismatch / metadata-invisible constraint (mcp-server-sqlite)

read_query is described as "Execute a SELECT query on the SQLite database" and its input schema is {query: string}. Nothing in the metadata constrains the query to a SELECT. The read-only guarantee exists only as a runtime check in the handler (if not query.strip().upper().startswith("SELECT"): raise).

We checked for a real bypass and found none under default settings: sqlite3 blocks stacked statements, and load_extension is disabled by default, so a metadata-blind "read tool that writes" does not actually execute a write here. So this is not a server vulnerability. But it is a real auditor blind spot: from MCP metadata alone (which is all a scanner or a consuming agent sees), read_query is indistinguishable from an unconstrained SQL sink. The safety lives in code the auditor never reads. Rule to add: when a tool's description names a narrow operation ("SELECT", "read", "docs only") but its schema accepts a free-form param of a broader type (arbitrary SQL/path/command string), flag it as excessive-agency-unless-proven: the metadata cannot establish the constraint.

The positive contrast (mcp-shell-server)

shell_execute is exactly the kind of tool you'd expect to be dangerous, and our scorer flags its command param. But its description surfaces the actual runtime constraint: "Allowed commands: ls, echo": the allowlist, reflected live from config, right in the metadata. This is the correct pattern and the direct inverse of sqlite's invisible guard: if a tool has a runtime constraint, put it where the auditor and the agent can see it. A good scanner should reward this (the disclosed allowlist downgrades the flag), and we'll encode that.

Honest grading summary

  • Clean / expected-capability: git, filesystem paths, time, sqlite write_query, shell_execute (disclosed allowlist). Correct, non-findings.
  • New classes, real-world-derived: fetch behavioral-override (A), sqlite operation-scope mismatch (B).
  • No exploitable bug, no disclosure needed this batch. These are methodology + tooling results: the two new classes become rules that make the auditor catch what a fixture set alone never would.

Scope + method

Local, sandboxed, discovery-only. Servers installed pinned into a throwaway venv, never invoked. Deterministic scorer, zero model spend. Not an Anthropic submission (third-party MCP servers are out of that program's scope). Public reference writeup; no server here needed private disclosure.

Next batch

More community + framework surfaces; encode classes A and B into the scorer (via cipher) and re-audit fetch + sqlite to confirm recovery.

MCP audit series

  1. Part 1MCP Audit Research Series, Batch 16 real servers, 2 new detection classes
  2. Part 2MCP Audit Research Series, Batch 2False positives are the next frontier, not recall
  3. Part 3MCP Excessive Agency in the Wild9 real servers, 5 detection classes, 0 exploitable bugs
All 14 writeups