## https://sploitus.com/exploit?id=D21671DF-D3F5-5162-9FDD-5522622D2C26
# mcpshield

[](LICENSE)
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](#scope) first, then [Install](#install)
and [Getting started](#getting-started) will get you protected in under two
minutes.
## Table of contents
- [Project status](#project-status)
- [Scope](#scope)
- [Install](#install)
- [Getting started](#getting-started)
- [Option A: the autopatch (Python MCP hosts)](#option-a-the-autopatch-python-mcp-hosts)
- [Option B: static check (any language, zero execution)](#option-b-static-check-any-language-zero-execution)
- [Option C: launch supervisor (non-Python hosts)](#option-c-launch-supervisor-non-python-hosts)
- [What gets blocked vs. what gets a warning](#what-gets-blocked-vs-what-gets-a-warning)
- [The escape hatches](#the-escape-hatches)
- [CLI reference](#cli-reference)
- [Known limitations](#known-limitations)
- [Project layout](#project-layout)
- [Development](#development)
## Project status
Pre-1.0, actively developed.
- Validation engine, autopatch, and CLI (`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`.
- Not yet on PyPI -- see [Install](#install).
- See [SECURITY.md](SECURITY.md) for exactly what is and is not covered.
## Scope
**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](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.
## Install
```bash
git clone
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:**
```bash
mcpshield --version
mcpshield --help
```
## Getting started
### Option A: the autopatch (Python MCP hosts)
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`:
```python
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.
### Option B: static check (any language, zero execution)
Audit an `mcpServers`-style config file without running anything:
```bash
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.
### Option C: launch supervisor (non-Python hosts)
For an MCP client (Node, Java, Rust, ...) that can't use the Python
autopatch, point its config at `mcpshield` instead of the real command:
```json
{
"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.
## What gets blocked vs. what gets a warning
| 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.
## The escape hatches
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.
## CLI reference
| Command | What it does |
|---|---|
| `mcpshield check [--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 -- [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. |
## Known limitations
- Not yet on PyPI -- install requires `git clone`.
- The autopatch only patches `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.
- The shell-metacharacter check is deny-list based, applied only when the
resolved command is detected as shell-interpretable. It is not a full
shell-grammar parser -- see [SECURITY.md](SECURITY.md) for the exact
scope boundary.
- `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.
## Project layout
```
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
```
## Development
```bash
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.