The Model Context Protocol (MCP) lets AI assistants and agents connect to tools and data through small programs called MCP servers. On April 15, 2026, OX Security disclosed a systemic problem in the STDIO transport, the mode where a client launches a server as a local process. If an attacker can influence the server configuration, they may be able to run arbitrary shell commands.

This article explains how that class of bug works, what the disclosures showed, and which practical controls reduce the risk.

What the STDIO Issue Is and Why It Spread

With the STDIO transport, an MCP client spawns the server as a local subprocess and talks to it over standard input and output. The MCP project's own security policy describes this model and lists injection flaws in official SDK implementations as a vulnerability category. A typical configuration entry names a command, such as npx, plus its arguments.

The problem is that the configuration is treated as trusted, yet it can be influenced by others. According to Cloud Security Alliance (CSA) research notes, user-supplied configuration values reach operating-system command execution without sanitization. The notes say this affects Anthropic's official MCP SDK implementations in Python, TypeScript, Java and Rust.

The scale is what made this a systemic issue. The CSA note reports more than 150 million package downloads, roughly 7,000 publicly reachable servers, an estimated 200,000 vulnerable deployments, and at least 14 assigned CVEs, up from 10 to 11 at initial disclosure. One report says Anthropic called the behavior by design and placed responsibility for sanitizing inputs between configuration and process spawning on developers. The same report says real-world exploitation was not quantified at disclosure.

How the Attacks Work

The research describes four exploitation families:

  • Unauthenticated command injection through the web interfaces of frameworks that build MCP configurations.
  • Allowlist bypass through argument injection, where an approved command is abused through its options.
  • Zero-click prompt injection in developer IDEs, where hostile content steers the tool into changing configuration.
  • Malicious packages distributed through unreviewed registries. OX Security reported that nine of eleven tested MCP registries could be poisoned.

The allowlist bypass is the easiest to misjudge. A CSO Online report notes that allowing npx is not enough, because its -c option can pass custom commands or shell scripts. Approving a program name is not the same as approving what it will do.

A related case is CVE-2025-6514 in mcp-remote. Values returned by a remote server during OAuth authorization were insufficiently validated before being used to build a local command. Here the untrusted input came from the server side rather than a config file, but the root cause was the same: external text ended up in a command.

Hardening Practices That Hold Up

The CSA recommends reviewing how configuration is handled, keeping configuration files under version control with required review, and isolating unavoidable STDIO subprocesses. Those ideas translate into a few concrete rules.

Do not let configuration define the command line

Let configuration pick a vetted server by name, and keep the executable and fixed arguments in code. Never use a shell, and reject option-like extra arguments. The following Python sketch shows the pattern:

import subprocess

# Vetted launch recipes live in code, not in user-editable config.
SERVERS = {
    "files": ["/usr/bin/node", "/opt/mcp/files/server.js"],
}

def spawn_stdio_server(name: str, extra_args: list[str]) -> subprocess.Popen:
    base = SERVERS.get(name)
    if base is None:
        raise ValueError(f"Unknown MCP server: {name!r}")
    # Treat extra values as data only; refuse anything that looks like an option.
    for arg in extra_args:
        if arg.startswith("-"):
            raise ValueError(f"Option-like argument rejected: {arg!r}")
    return subprocess.Popen(
        base + extra_args,
        shell=False,
        env={"PATH": "/usr/bin"},  # do not inherit secrets from the parent
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )

The minimal environment matters too. A spawned server that inherits API keys from its parent can leak them even without a full compromise.

Isolate what you cannot avoid

If a server must run as a subprocess, limit the damage it can do. Containers are one option, combined with least privilege and restricted network access. For example:

docker run --rm -i \
  --network none \
  --read-only \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --user 65534:65534 \
  my-mcp-server:1.0

The -i flag keeps standard input open, which STDIO needs. Drop --network none only if the server truly needs network access, and then restrict it with a network policy.

Treat the supply chain as part of the attack surface

  • Install servers only from sources you have reviewed, and pin versions.
  • Keep configuration files in version control and require review of changes.
  • Do not let an assistant edit its own MCP configuration without human approval.
  • Validate anything returned by a remote server before it goes near a command line.

Conclusion

The STDIO disclosures show that for MCP, configuration is code execution. Whether the flaw is called a vulnerability or a design choice, the practical result is the same: any path that lets untrusted text shape a launch command is dangerous.

Fixed launch recipes, no shell, minimal environments, sandboxed processes and reviewed configuration changes together remove most of that exposure. Each control covers a gap the others leave open.