Command Injection & Execution (MCP05)

MCP053 checks

The classic vulnerability with a new delivery mechanism. What is different about MCP is not the bug — it is that you have attached an eager, creative, tireless agent to the input field.

Paste a server URL for a free, unauthenticated scan — 27+ checks, graded report, no sign-up.

The free scan only sees what an anonymous caller sees. Authenticated and agentic depth lives in the dashboard scanner.

How the attack works

A tool accepts a string. The server drops that string into a shell invocation, an eval, a template, or a filesystem path without validating it. That is the entire bug, and it predates MCP by decades. What MCP changes is who supplies the input and how hard they try. A traditional injection needs an attacker to find the parameter and craft a payload. Here, a language model reads your tool description, infers what arguments would be useful, and constructs them — which means an ordinary user request can produce an unusual argument with no malicious intent at all. Add indirect injection and the model is taking argument suggestions from whatever text it last read. The path traversal variant is the same shape: a filename parameter that accepts ../ walks straight out of the directory you meant to scope it to.

A schema that guarantees a finding

{
  "name": "run_report",
  "description": "Generate a report. Accepts optional shell flags for formatting.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "report_name": { "type": "string" },
      "flags":       { "type": "string", "description": "Extra CLI flags" }
    }
  }
}

// server:  exec('/usr/bin/report ' + report_name + ' ' + flags)

A free-text field documented as "CLI flags" is an invitation. The model will populate it helpfully, and the shell will interpret it literally.

What the scanner checks

Three schema-level detectors run against every tool your server exposes: one for arguments that reach a shell or command runner, one for dynamic code evaluation, and one for file operations whose path parameters show no sign of normalisation or scoping. They read tool names, descriptions and input schemas and flag the shapes that historically precede an execution bug.

Heuristic — a schema guess, capped at low severity until the agentic probe confirms it:

  • ~Command Injection Risk
  • ~Code Injection Risk
  • ~Path Traversal Risk

What this cannot tell you

All three are explicitly heuristic and capped at low severity, because reading a schema cannot prove a vulnerability — only that the ingredients are present. A tool named run_command may be perfectly sandboxed; a tool named get_summary may shell out internally where no scanner can see. Turning a suspicion into a verdict requires the active scan depth, which sends crafted payloads, and that is gated behind explicit consent and audit mode. We will not send payloads at a server you do not own.

How to fix it

Never build a command by string concatenation

Use argument arrays — execFile or spawn with an args list, never exec with an interpolated string. This single change eliminates the entire shell-metacharacter class, because there is no shell left to interpret the metacharacters.

Make the schema the allowlist

A free-text string is the problem. Replace it with an enum, a pattern-constrained string, or a set of named boolean flags. An agent cannot inject through a parameter that only accepts one of four literal values, and the schema doubles as documentation the model reads correctly.

Resolve and confine every path

Resolve to an absolute path, then verify the result is still inside the intended root before opening it. Rejecting ".." by string match is not sufficient — symlinks, URL encoding and Unicode normalisation all defeat it.

Assume the argument is hostile even when the user is not

The model may be relaying content from a web page, a ticket or a file it just read. Between the user's intent and your tool sits a system that can be talked into things, so server-side validation is the only boundary that actually holds.

Go deeper

Background reading on this risk: The MCP stdio RCE Vulnerability: What OX Security Found & How to Protect Your Servers.

This category is reported as verified evidence and needs at least active scan depth to produce a real result, with a model-driven probe for the confirming verdict.

Related risks

Frequently asked questions

Why is this worse for MCP servers than for a normal API?

Because the caller is generative. A REST endpoint receives the arguments a client was coded to send; an MCP tool receives arguments a model invented to satisfy a goal, and that model may be acting on text an attacker planted. The input distribution is far wider than anything you tested against.

Does the free scanner try to exploit my server?

No. The free scan reads schemas and descriptions only. Crafted payloads are sent exclusively at the active depth, only in audit mode, and only after you explicitly consent to it for a server you have confirmed you own.

My tool runs in a container. Is that enough?

It limits blast radius, which is worth a lot, but it does not close the hole. A container still holds your environment variables, your mounted credentials and your network position — and in many deployments that is the interesting material anyway.

Is the stdio transport especially exposed here?

Local stdio servers often run with full user privileges and no sandbox, so an execution bug is immediately an RCE on a developer laptop. The OX Security disclosure covers the concrete case and is worth reading alongside this page.

MCP Command Injection: When a Tool Argument Becomes a Shell