# MCPlexer > Secure, audit, and control every AI tool call across your organization. One gateway. Complete visibility. Zero risk. MCPlexer is an MCP gateway that sits between your AI client (Claude Desktop, Claude Code, Cursor, …) and your downstream MCP servers. It enforces directory-scoped routing, human-in-the-loop approvals, OAuth credential injection, audit logging, and shell-injection-resistant downstream spawning. The product is local-first, encrypts secrets at rest with age, and ships open source under MIT. Operated and supported by Revitt. Site: https://mcplexer.com Source: https://github.com/RevittCo/mcplexer Booking: https://revitt.co/book --- # How MCPlexer mitigates the MCP STDIO RCE class > Published 2026-05-04 by Max Revitt · Category: security In April 2026, OX Security published a [supply-chain advisory](https://www.ox.security/blog/mcp-supply-chain-advisory-rce-vulnerabilities-across-the-ai-ecosystem/) showing that the Model Context Protocol's STDIO transport — the default for connecting an AI agent to a local tool — executes whatever operating-system command a downstream config declares. No sanitisation. No execution boundary between configuration and command. The behaviour propagated into every official language SDK (Python, TypeScript, Java, Rust) and every downstream project that trusted the protocol. Estimated exposure (per OX): **200,000 servers, 150M+ downloads, 10+ high or critical CVEs**, with arbitrary command execution confirmed against six platforms including LangFlow, LiteLLM, Flowise, and Upsonic. Cursor, Windsurf, Claude Code, and Gemini-CLI are vulnerable to the broader family. Windsurf received CVE-2026-30615 for the zero-click variant. Anthropic responded that **the STDIO execution model is by design** and that **command sanitisation is the host's responsibility**. They updated SECURITY.md but made no architectural changes. MCPlexer is exactly the kind of host the disclosure targets — every configured stdio downstream becomes an `exec.CommandContext` call. We treat it as our responsibility. This post is the receipt. ## The four exploitation families OX identified 1. **Unauthenticated command injection** through AI-framework web interfaces. Demonstrated against LangFlow and LiteLLM by submitting a downstream config with `command: bash` and `args: ["-c", ""]`. The framework happily spawned it. 2. **Allowlist bypass** in tools that restricted commands to known runners. Demonstrated against Flowise and Upsonic by passing `npx -c ''` — npx's `-c` flag forwards to a shell, so the allowlist of "only npx" was meaningless. 3. **Zero-click prompt injection** in AI coding IDEs. Malicious HTML rendered by the agent modifies its own `mcp_config.json` to register a malicious server. Next launch, RCE. 4. **Argument injection at execution time**. Even when a runner's argv is parameterised, several SDKs concatenated the command and args into a single shell-evaluated string. `exec`-style spawning closes this vector; `system()`/`shell=True` opens it. ## Where MCPlexer was exposed MCPlexer's downstream subprocess spawn lives in `internal/downstream/instance.go`: ```go cmd := exec.CommandContext(childCtx, cmdPath, inst.args...) cmd.Env = inst.env ``` Go's `exec.Command` uses `execve` rather than a shell, so the **single-string concatenation** family (#4) doesn't apply directly. But the **command field itself** (`inst.command`) was previously taken on trust. Anyone who could write a downstream config — via the dashboard, the YAML loader, the MCP control protocol, or addon imports — could register `command: bash` and own the host. The dashboard is the obvious attack surface for #1. With the [API auth gate landed in #16](https://github.com/RevittCo/mcplexer/pull/16), every `/api/v1/downstreams` write requires the per-install bearer token. That closes #1 against the network. But defence-in-depth still pays: * The API token can be exfiltrated via prompt-injection of an already-trusted MCP server. * A YAML typo (`command: rm`) shouldn't fail-open into RCE. * Addons and the MCP control protocol can register downstreams without the same human eyeballs as the dashboard. So the API-auth gate is necessary but not sufficient. The full mitigation set: ## Six concrete controls ### 1. Authenticated control plane Every `/api/v1/*` and `/api/p2p/*` request requires a per-install token, supplied as an HttpOnly `mcplexer_session` cookie to the dashboard or as a `Authorization: Bearer` header to scripts. The token is generated at first start and persisted to `~/.mcplexer/api-key` with mode 0600 — only the daemon's UID can read it. `/api/v1/health` and `/api/v1/oauth/callback` are the only exceptions (liveness probes, IDP redirects). ### 2. Downstream command guard Code at [`internal/downstream/cmdguard.go`](https://github.com/RevittCo/mcplexer/blob/main/internal/downstream/cmdguard.go). Three layers: ```go // Reject shells as the command field. shellCommandBasenames = {sh, bash, dash, zsh, ksh, ash, csh, tcsh, fish, eval, exec, source, cmd, cmd.exe, powershell, powershell.exe, pwsh} // Reject shell-eval flags as arguments. shellEvalArgs = {-c, -e, --call, --eval, --exec, --execute, --code, --inline, -Command, -EncodedCommand} // Reject shell metacharacters in the command string. shellMetaChars = ";|&`$\n\r" // Reject parent-directory traversal. strings.Contains(command, "..") ``` The shell allowlist defeats `command: bash` directly. The eval-flag block defeats OX's `npx -c` argument-injection bypass — and the equivalent for `node -e`, `python -c`, `deno --eval`, `php --code=`, and PowerShell `-Command`. The metacharacter check defeats config-string injection (`command: "npx; curl evil"`). Path traversal is rejected because no legitimate runner spec needs `..`. ### 3. Validate at registration AND spawn The same guard runs in two places: * The API write path: `internal/api/downstream_handler.go` calls `ValidateCommand` on every `POST` and `PUT` to `/api/v1/downstreams` before persisting. * The spawn path: `internal/downstream/instance.go` calls `ValidateCommand` immediately before `exec.CommandContext`. A malicious config in your YAML, your DB, your seeded data, or anywhere else can never reach `exec.Command`. ### 4. No env passthrough to subprocesses `internal/downstream/env.go` strips daemon-private env vars before spawning a downstream. Specifically: ```go sensitiveDaemonEnvKeys = {AGE_IDENTITY, AGE_KEY, AGE_PASSPHRASE, MCPLEXER_PROVIDER} sensitiveDaemonEnvPrefixes = {"MCPLEXER_"} ``` Without this strip, every `npx`-launched MCP server inherited `MCPLEXER_AGE_KEY` and could decrypt the SQLite secrets blob — the worst case for a benign-but-buggy downstream and the entire game for a malicious one. ### 5. Sandboxed code execution The `mcpx__execute_code` tool runs JavaScript inside a Goja VM with both a recursion cap and a heap-growth watchdog (`internal/codemode/sandbox.go`): ```go const ( defaultMaxCallStack = 256 defaultMaxHeapGrowthMB = 256 defaultWatchdogPeriod = 50 * time.Millisecond ) ``` A `while(true) a.push(x)` loop interrupts in tens of milliseconds — measured against the previous behaviour, which OOM'd the daemon long before the wall-clock timeout fired. ### 6. Approval gate without a backdoor Self-approval (resolver session matching requester) is rejected for every approver type, including `dashboard`. The previous behaviour short-circuited the check for dashboard resolves, which let any caller in possession of the API token self-approve their own MCP request. That short-circuit is removed in `internal/approval/manager.go`. The dashboard handler now derives a stable approver-session ID from a hash of the auth token so dashboard resolves are still distinguishable from MCP sessions. ## Defence in depth These six controls don't rely on each other: * The command guard would block a shell config even if the API token were exfiltrated. * The env strip holds even if a downstream server is malicious. * The auth gate keeps random local processes off the API in the first place. * The sandbox limits keep a compromised Code Mode script from taking the daemon down. Power users who genuinely need a shell as a downstream can set `MCPLEXER_UNSAFE_DOWNSTREAM_COMMANDS=1` with informed consent — the audit trail records which configs are non-allowlisted. ## What you can verify The full hardening landed in [PR #16](https://github.com/RevittCo/mcplexer/pull/16) and the STDIO-specific guard in the same PR's last commit. To audit: ```bash # The command guard — read it. curl https://raw.githubusercontent.com/RevittCo/mcplexer/main/internal/downstream/cmdguard.go # The env strip. curl https://raw.githubusercontent.com/RevittCo/mcplexer/main/internal/downstream/env.go # Tests covering both. git clone https://github.com/RevittCo/mcplexer cd mcplexer go test ./internal/downstream/... -v -run "TestValidateCommand|TestMergeEnvStripsDaemonSecrets" ``` Live smoke against your local install: ```bash TOKEN=$(cat ~/.mcplexer/api-key) # Should 400 with "downstream command bash is a shell interpreter" curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"id":"x","name":"x","transport":"stdio","command":"bash","args":["-c","id"],"tool_namespace":"x"}' \ http://127.0.0.1:13333/api/v1/downstreams # Should 400 with "downstream command argument -e lets the runner evaluate arbitrary code" curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"id":"y","name":"y","transport":"stdio","command":"node","args":["-e","process.exit(0)"],"tool_namespace":"y"}' \ http://127.0.0.1:13333/api/v1/downstreams ``` ## References * OX Security · [MCP Supply Chain Advisory: RCE Vulnerabilities Across the AI Ecosystem](https://www.ox.security/blog/mcp-supply-chain-advisory-rce-vulnerabilities-across-the-ai-ecosystem/) * OX Security · [Critical, Systemic Vulnerability at the Core of Anthropic's MCP](https://www.ox.security/blog/the-mother-of-all-ai-supply-chains-critical-systemic-vulnerability-at-the-core-of-the-mcp/) * OX Security · [Technical deep dive](https://www.ox.security/blog/the-mother-of-all-ai-supply-chains-technical-deep-dive/) * VentureBeat · [200,000 MCP servers expose a command execution flaw that Anthropic calls a feature](https://venturebeat.com/security/mcp-stdio-flaw-200000-ai-agent-servers-exposed-ox-security-audit) * The Hacker News · [Anthropic MCP Design Vulnerability Enables RCE, Threatening AI Supply Chain](https://thehackernews.com/2026/04/anthropic-mcp-design-vulnerability.html) * The Register · [MCP 'design flaw' puts 200k servers at risk](https://www.theregister.com/2026/04/16/anthropic_mcp_design_flaw/) * Cloud Security Alliance · [Lab Note: MCP by Design — RCE Across the AI Agent Ecosystem](https://labs.cloudsecurityalliance.org/research/csa-research-note-mcp-by-design-rce-ox-security-20260420-csa/) ## FAQ **Is MCPlexer affected by CVE-2026-30615?** No. CVE-2026-30615 is a Windsurf-specific finding (zero-click via malicious HTML rewriting `mcp_config.json`). MCPlexer doesn't load configs from page content; configs are sourced from the YAML file, the dashboard, the MCP control protocol, or addon definitions, all of which now flow through the command guard. **Does `MCPLEXER_UNSAFE_DOWNSTREAM_COMMANDS=1` weaken security irreversibly?** It bypasses the command guard at the spawn site only. The API auth gate, env strip, sandbox limits, and approval-gate fixes remain in effect. We recommend the override only for local scripting experiments; production deployments should never set it. **Why not block all dynamic downstream registration entirely?** Because the value of MCPlexer comes from being able to register any MCP server you want. The threat model is "untrusted *content*", not "untrusted *user*" — the user holds the API token. The command guard lets the user register any legitimate MCP runner while blocking the specific patterns that turn a config into RCE. **How can I report a vulnerability?** Open a [private security advisory](https://github.com/RevittCo/mcplexer/security/advisories/new) on GitHub or email security@revitt.co. ---