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

![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](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.