Breach report
The MCP server RCE pattern: when an AI's tool runs your words through a shell
What happened
The Model Context Protocol (MCP) is the standard that lets AI agents call external tools. Through 2025 a repeating vulnerability appeared across popular MCP servers: a server takes a parameter the language model fills in and passes it to a shell — typically Node's child_process.exec — without sanitizing it. Because an attacker can influence what the model produces through indirect prompt injection, they can smuggle shell metacharacters into that parameter and make the server run arbitrary commands on the host.
The pattern was confirmed in multiple widely used servers, each with its own CVE: the Git MCP server (CVE-2025-53107), a Figma developer MCP server (CVE-2025-53967), an Android Debug Bridge MCP server (CVE-2025-59834), and a Serverless Framework MCP server (CVE-2025-69256), among others. It is not one bug — it is a systemic design mistake replicated across the ecosystem.
Root cause
Model and tool output was treated as trusted input to a shell. The server authors assumed the argument was benign because "the AI generated it," but the AI's output is attacker-influenceable. Stripped of the AI framing, this is classic OS command injection (CWE-78) — OWASP LLM05 Improper Output Handling, triggered by LLM01 Prompt Injection.
How it would have been caught
Static analysis rules (Semgrep, CodeQL) that flag child_process.exec, os.system, eval, or shell calls on tool-parameter data would have caught the pattern at the source. Software Composition Analysis catches the specific CVEs in dependencies. The reproduction is a single call to the tool with an argument such as ; id or $(...), asserting the injected command does not run.
How to prevent it
- Never pass model or tool data to a shell. Use argument-array APIs (
execFile,spawnwithout a shell) or a strict allowlist with escaping. - Run tool execution under a least-privilege, egress-jailed sandbox so even a successful injection cannot reach the network or protected files.
- Screen tool inputs with an injection classifier and keep untrusted content boxed.
The Breachwire test (red → green)
Call the tool with a metacharacter payload and confirm the injected command executes (the vector is real). Switch to a no-shell argument array and sandbox the execution, then confirm the same payload is treated as a literal argument and does nothing, while a legitimate tool call still succeeds.