
Drop-in fix for the unpatched MCP STDIO command-injection flaw (CVE-2026-30623 family)
A drop-in fix for the unpatched MCP STDIO command-injection flaw (the CVE-2026-30623 family, disclosed by OX Security in April 2026 as "by design" -- no SDK patch is coming). Import one line, and every stdio MCP server your Python app launches gets its command/args/env validated before the OS ever spawns a process.
If you're new here, read Scope first, then Install and Getting started will get you protected in under two minutes.
Pre-1.0, actively developed.
check/launch/rules) are
implemented and covered by an automated test suite that runs against the
real binaries installed on the test machine (python, node, npx) --
not mocks -- including a genuine end-to-end MCP handshake through a real
spawned server fixture, and a genuine subprocess-level test of launch.In scope: validating a stdio MCP server launch (command + args + env) before it reaches the OS process-spawn layer, specifically to close the command/argument-injection path described in SECURITY.md.
Explicitly out of scope: scanning a server's declared tools for risky capabilities (that's a different problem -- see AgentGuard), sandboxing the spawned process, and non-stdio (SSE/HTTP) MCP transports.
git clone <this-repo>
cd mcpshield
pip install -e . # core CLI: click + rich only
pip install -e ".[mcp]" # if you also want the Python autopatch (needs the `mcp` SDK)
Verify it worked:
mcpshield --version
mcpshield --help
If your app is written in Python and builds StdioServerParameters /
calls mcp.client.stdio.stdio_client itself, add one import at the very
top of your entrypoint -- before anything else imports mcp.client.stdio:
import mcpshield.autopatch # side-effect import; must come first
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
# ... use stdio_client exactly as before -- it's now validated
An unsafe launch now raises mcpshield.core.errors.UnsafeConfigurationError
(a ValueError subclass) instead of ever spawning a process.
Audit an mcpServers-style config file without running anything:
mcpshield check claude_desktop_config.json
+---------------------------------------------------------------+
| Server | Status | Command | Detail |
|------------------+---------+---------+------------------------|
| filesystem | OK | npx | - |
| evil-server | BLOCKED | npx | Argument '...' contains|
| | | | shell metacharacter |
+---------------------------------------------------------------+
1 ok, 0 warned, 1 blocked
Exits non-zero if anything is BLOCKED (add --strict to also fail on
WARN) -- drop it straight into CI.
For an MCP client (Node, Java, Rust, ...) that can't use the Python
autopatch, point its config at mcpshield instead of the real command:
{
"command": "mcpshield",
"args": ["launch", "--", "npx", "-y", "some-mcp-server"]
}
launch validates, then executes the real command with the same stdio
your MCP client expects (transparent passthrough) -- or refuses with a
clear error if the launch is unsafe.
| Check | Native binary (e.g. python.exe) | Shell-interpretable (.cmd/.bat/shebang script) |
|---|---|---|
Shell metacharacters (&, |, ;, backtick, $(...), ...) in an argument | Allowed | Blocked |
| NUL byte / newline in an argument | Blocked | Blocked |
Command resolves via relative path traversal (..) | Blocked | Blocked |
| Command doesn't resolve to a real file | Blocked | Blocked |
LD_PRELOAD / NODE_OPTIONS / etc. in env | Stripped (warning) | Stripped (warning) |
PYTHONPATH in env | Flagged (warning), not stripped | Flagged (warning), not stripped |
Native binaries get looser argument checks because they exec directly --
there's no shell to re-parse the argument list. Shell-interpretable
commands (most commonly npx.cmd/npx.bat on Windows) get strict checks
because that's the exact mechanism the underlying CVE exploits.
Both are deliberate, per-value opt-ins -- never a blanket "disable checks" flag:
allow_raw_args=["--some-value-with-a-pipe"] (library) exempts specific
argument values you've reviewed and trust.allow_env=["SOME_VAR"] permits a normally-stripped environment variable
to pass through unmodified.| Command | What it does |
|---|---|
mcpshield check <config> [--format table|json] [--strict] | Static audit of an mcpServers config. Never executes anything. Non-zero exit on any BLOCKED (or WARN too, with --strict). |
mcpshield launch -- <command> [args...] | Validates, then executes the real command with passthrough stdio. |
mcpshield rules list | Shows the active shell-metacharacter blocklist, environment-variable lists, and known safe launcher binaries. |
git clone.mcp.client.stdio.stdio_client as looked up
at patch time. Code that already holds its own reference (via
from mcp.client.stdio import stdio_client executed before
import mcpshield.autopatch) will bypass it -- import mcpshield.autopatch
first, always.check resolves commands using the machine it runs on. A config that
would resolve differently on the machine it's actually deployed to (a
different PATH, different installed tools) may report differently there.mcpshield/
autopatch.py # one-line-import fix for Python MCP hosts
core/
validate.py # the validation engine (command/args/env checks)
rules.py # blocklist/allowlist data
errors.py # UnsafeConfigurationError
cli/
main.py
commands/ (check.py, launch.py, rules.py)
tests/
fixtures/ # real benign MCP server + sample/malicious configs
pip install -e ".[dev,mcp]"
pytest
The test suite validates against the real python/node/npx binaries
installed on the machine it runs on (resolved the same way the engine
itself resolves them), and includes a genuine end-to-end MCP handshake
through a real spawned server fixture -- not mocks.