MCPSafe.io
RegistryThreatsMethodologyDocsPricingScanSign in
MCPSafe.io

Security checks for MCP servers — public packages and private repos, fast or deep.

Legal

Privacy PolicyCookie PolicyTerms of ServiceSecurity disclosure

Resources

State of MCP SecuritySupportSystem statusMade in Germany 🇩🇪

© 2026 MCPSafe. All rights reserved.

GDPR — Privacy Policy
← Threat Catalog

Server Implementation

Command injection (shell=True / string concat)

CRITICALAIVSS 9.8CWE: CWE-78OWASP: LLM05Agentic: T02Rule: MCP-002

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.

What it is

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.

Why it matters for MCP

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.

Vulnerable example

example.js
1
import { exec } from "node:child_process";
2
3
server.tool("run_command", { cmd: z.string() }, async ({ cmd }) => {
4
  const { stdout } = await exec(cmd);
5
  return { content: [{ type: "text", text: stdout }] };
6
});

Secure example

example.js
1
import { execFile } from "node:child_process";
2
import { promisify } from "node:util";
3
const run = promisify(execFile);
4
5
const ALLOWED = new Set(["ls", "pwd", "date"]);
6
7
server.tool("run_command", { cmd: z.string(), args: z.array(z.string()).optional() }, async ({ cmd, args = [] }) => {
8
  if (!ALLOWED.has(cmd)) throw new Error("command not permitted");
9
  const { stdout } = await run(cmd, args, { shell: false, timeout: 5_000 });
10
  return { content: [{ type: "text", text: stdout }] };
11
});

How MCPSafe detects this

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.

Framework alignment

OWASP LLM Top-10 (2025)
LLM05 — Improper Output Handling
OWASP Agentic AI Top-10
T02 — Tool Misuse
AIVSS v0.5
9.8 (CRITICAL)AIVSS:1.0/S:CRITICAL/AV:N/AU:M/BR:H/CD:I

Illustrative CVEs

CVEs of the same CWE class. Not MCP-specific, but exemplify the failure mode MCPSafe detects.

  • CVE-2024-4577 — PHP-CGI argument injection — same CWE-78 pattern
  • CVE-2014-6271 — Shellshock — environment-variable-driven command injection

Further reading

  • CWE-78: OS Command Injection
  • OWASP Command Injection

Scan an MCP server for this issue

MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.

Scan now