Server Implementation
An MCP tool handler concatenates model-supplied input into a shell command, letting an attacker run arbitrary commands on the host. Argv-style invocations on dangerous CLIs are covered separately by MCP-276 (flag injection) — the two rules co-fire when both shapes are present.
Command injection happens when user- or model-supplied text is interpolated into a shell invocation without being treated as data. The shell interprets metacharacters (`;`, `|`, backticks, `$(...)`) and executes whatever comes after, giving the attacker code execution as the server process. This is one of the oldest web-application bugs and it has not gotten any rarer in MCP servers.
An MCP server is, by design, a programmable surface that an LLM drives. The model's output is partially controlled by whoever authored the prompt the user pasted — or by retrieved content from the web. That means the "attacker" is often not a human poking at your API: it is a chat agent that happily passes an injected string to your tool because the prompt told it to. Classical input validation assumed a human adversary who had to try; here the adversary is an LLM that will try anything asked of it.
import { exec } from "node:child_process"; |
server.tool("run_command", { cmd: z.string() }, async ({ cmd }) => { |
const { stdout } = await exec(cmd); |
return { content: [{ type: "text", text: stdout }] }; |
}); |
import { execFile } from "node:child_process"; |
import { promisify } from "node:util"; |
const run = promisify(execFile); |
const ALLOWED = new Set(["ls", "pwd", "date"]); |
server.tool("run_command", { cmd: z.string(), args: z.array(z.string()).optional() }, async ({ cmd, args = [] }) => { |
if (!ALLOWED.has(cmd)) throw new Error("command not permitted"); |
const { stdout } = await run(cmd, args, { shell: false, timeout: 5_000 }); |
return { content: [{ type: "text", text: stdout }] }; |
}); |
MCPSafe flags any call to `child_process.exec`, `os.system`, `subprocess.run(..., shell=True)`, or the equivalent in seven other languages when a tool-handler argument reaches it via data flow. We ignore constant strings and `execFile`-style calls with argv arrays, which are the safe pattern.
See the full threat catalog for every documented detection.
CVEs of the same CWE class. Not MCP-specific, but exemplify the failure mode MCPSafe detects.
MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.
Scan now