Defense-in-depth-Bundle für MCP-stdio-Server: Drop-in-Wrapper für guardExec/guardSpawn, AST-Audit-CLI, Referenz-MCP-Server. Schließt die von Ox-Security identifizierte stdio-RCE-Klasse, von der 200.000 Server betroffen sind (LiteLLM CVE-2025-69256). MIT, TypeScript, Node >= 20.
<!-- studiomeyer-mcp-stack-banner:start -->
> **Teil des [StudioMeyer MCP Stack](https://studiomeyer.io)** — Gebaut auf Mallorca 🌴 · ⭐ wenn du es nutzt
<!-- studiomeyer-mcp-stack-banner:end -->
# mcp-stdio-shellguard
<!-- badges -->
[](https://www.npmjs.com/package/mcp-stdio-shellguard)
[](https://www.npmjs.com/package/mcp-stdio-shellguard)



<!-- /badges -->Defense-in-depth-Bundle für MCP-stdio-Server. Umschließt `child_process.exec/spawn`
mit Allowlist + Sandbox + Replay-Erkennung, plus eine AST-Audit-CLI (`mcp-shellguard-audit`),
die MCP-Server-Quellen nach unbereinigten Shell-Aufrufen durchsucht. Schließt die Ox-Security
MCP-stdio-RCE-Klasse (200k anfällige Server, Offenlegung Mai 2026).
- **MCP-Spezifikation**: 2025-06-18
- **SDK**: `@modelcontextprotocol/sdk` ^1.29.0
- **Node**: >= 20
- **Lizenz**: MIT
- **Autor**: Matthias Meyer (StudioMeyer)
## Installation
```bash
npm install mcp-stdio-shellguard
```
Oder führen Sie die Audit-CLI direkt ohne Installation aus:
```bash
npx -y -p mcp-stdio-shellguard mcp-shellguard-audit scan ./src
```
## Was es dir bietet
Drei Ebenen, optional und stückweise:
1. **Library-API** — direkt einsetzbare `guardExec` / `guardSpawn`, die du aus deinem
eigenen MCP-Server aufrufst. Default-Deny-Allowlist, Sandbox-Profile, Replay-Fenster.
2. **Audit-CLI** — `mcp-shellguard-audit scan <path>` durchläuft den AST und meldet
12 Anti-Patterns von LOW (`no timeout`) bis CRITICAL (`exec(\`...${userInput}...\`)`).
3. **Referenz-MCP-Server** — `mcp-stdio-shellguard-demo` stellt 8 Tools bereit,
sodass der MCP Inspector / Claude Desktop das Bundle direkt steuern kann.
## Tools (Referenz-Server)
| Tool | Typ | Zweck |
|------|------|---------|
| `guard_exec` | destruktiv | Abgesichertes `child_process.exec`. Erzwingt args[]-Vektor, Allowlist + Sandbox + Replay. Gibt `stdout`, `stderr`, `exitCode`, `canonicalHash`, `isReplay`, `trustTier` zurück. |
| `guard_spawn` | destruktiv | Abgesichertes `child_process.spawn`. Gibt SHA-256-Hashes von stdout/stderr statt vollständiger Inhalte zurück. Lehnt `shell:true` strikt ab. |
| `register_allowlist` | mutierend | Registriert einen Tool-Namen mit Executable + Args-Regex. Ohne Registrierung greift Default-Deny. |
| `audit_source` | schreibgeschützt | Durchsucht einen TS/JS-Pfad nach Shell-Injection-Anti-Patterns. Gibt `AuditFinding[]` + Zusammenfassung zurück. |
| `audit_report` | schreibgeschützt | Formatiert ein Audit-Ergebnis als markdown / json / SARIF 2.1.0. |
| `replay_check` | schreibgeschützt | Berechnet den kanonischen SHA-256-Hash für einen Aufruf und meldet, ob er bereits im Replay-Fenster liegt. |
| `sandbox_status` | schreibgeschützt | Meldet aktives Sandbox-Profil + konkrete Limits + cgroup-v2-Aktivierungsflag. |
| `trust_tier` | schreibgeschützt | Leitet die Stufe LOW/MEDIUM/HIGH/CRITICAL für ein registriertes Tool ab, plus Verbesserungshinweise. |
## Sandbox-Profile
| Profil | Timeout | Max. stdout | Max. stderr | FD-Budget | cgroup-v2 |
|---------|---------|-----------|-----------|-----------|-----------|
| `strict` | 5 s | 1 MB | 256 KB | 32 | ja (cpu/memory) |
| `standard` (Standard) | 30 s | 10 MB | 1 MB | 256 | ja |
| `permissive` | 5 min | 100 MB | 10 MB | 1024 | nein |
Der Aufrufer kann pro Aufruf über `timeoutMs` / `fdBudget` verschärfen. Der Aufrufer kann nicht
über das Profil hinaus erweitern.
## Trust-Stufen
| Stufe | Bedingung |
|------|-----------|
| LOW | Tool nicht registriert (Default-Deny) |
| MEDIUM | registriert, aber `argsPatterns` leer (beliebige Args erlaubt) |
| HIGH | `argsPatterns` gesetzt, aber Sandbox oder Replay-Tracker inaktiv |
| CRITICAL | argsPatterns + Sandbox + Replay alle aktiv |
Anhebung von LOW → CRITICAL durch Registrieren des Tools + Setzen von argsPatterns + Ausführen
über `guardExec`/`guardSpawn` (die immer Sandbox + Replay aktivieren).
## Library-Schnellstart
```ts
import {
AllowlistRegistry,
ReplayWindow,
guardExec,
} from "mcp-stdio-shellguard";
const registry = new AllowlistRegistry();
const replay = new ReplayWindow();
registry.register({
toolName: "git-log",
executable: "/usr/bin/git",
argsPatterns: ["^log$", "^--oneline$", "^-n$", "^\\d+$"],
sandboxProfile: "strict",
});
const result = await guardExec(
{
toolName: "git-log",
command: "/usr/bin/git",
args: ["log", "--oneline", "-n", "10"],
},
{ registry, replay },
);
console.log(result.stdout); // → commit lines
console.log(result.trustTier); // → "CRITICAL"
console.log(result.canonicalHash); // → 64-char SHA-256
```
## Audit-CLI
```bash
mcp-shellguard-audit scan ./src
mcp-shellguard-audit scan ./src --format sarif --output audit.sarif
mcp-shellguard-audit scan ./src --severity-floor HIGH # CI gate
```
Exit-Codes:
- `0` sauber (keine Befunde auf oder über der Schwelle)
- `1` Befunde vorhanden
- `2` Parse-/IO-Fehler
Benutzerdefinierte `ignoreGlobs`, die an `scanPath` oder das MCP-Tool `audit_source` übergeben werden, akzeptieren höchstens 128 Strings. Jeder String ist auf 1024 UTF-16-Codeeinheiten und 16 nicht-escapte öffnende Gruppierungszeichen (`(`, `{`, `[`) begrenzt. Eingaben über diesen Budgets lösen vor dem Matching einen kontrollierten Fehler aus.
## Anti-Pattern-Bibliothek (12 Regeln)
| ID | Schweregrad | Löst aus bei |
|----|----------|-------------|
| `exec_template_literal_with_input` | CRITICAL | `child_process.exec(\`ls ${x}\`)` |
| `exec_dynamic_string` | CRITICAL | `child_process.exec(cmd)` |
| `exec_sync_dynamic_string` | CRITICAL | `child_process.execSync(cmd)` |
| `eval_near_child_process` | CRITICAL | `eval(...)` |
| `function_constructor_near_child_process` | CRITICAL | `new Function(...)` |
| `spawn_dynamic_file_args` | HIGH | `spawn(bin, userArgs)` |
| `exec_file_dynamic` | HIGH | `execFile(bin, ...)` |
| `shell_true_option` | HIGH | `{ shell: true }` |
| `os_system_equivalent` | HIGH | `Deno.run` / `Bun.spawn` |
| `spawn_literal_dynamic_args` | MEDIUM | `spawn('git', userArgs)` |
| `unbounded_buffer` | LOW | exec ohne `maxBuffer` |
| `missing_timeout` | LOW | exec/spawn ohne `timeout` |
Der Scanner löst *umbenannte* `child_process`-Bindings vor dem Matching auf,
sodass die unten stehenden gefährlichen Formen auch dann erkannt werden, wenn der Aufruf
über einen Alias statt über ein literales `child_process.exec` erfolgt:
- `const execAsync = promisify(exec); execAsync(`...${x}`)`
- `import cp from "node:child_process"; cp.exec(`...${x}`)`
- `const { exec: sh } = require("child_process"); sh(`...${x}`)`
- `import { exec as run } from "node:child_process"; run(...)`
Synchrone Varianten (`spawnSync`, `execFileSync`) teilen ihre Async-Regeln,
und `shell_true_option` löst auch bei einer String-Shell (`{ shell: "/bin/sh" }`)
oder einem dynamischen Shell-Wert aus — nicht nur beim literalen `{ shell: true }`. Ein
`promisify` einer Nicht-child_process-Funktion, ein Destructure aus einem anderen
Modul und `{ shell: false }` bleiben sauber (keine False Positives).
## Pragmas
- `// shellguard:ignore-next-line` — unterdrückt einen Befund
- `// shellguard:ignore-file` — unterdrückt die ganze Datei (selten; pro Zeile bevorzugen)
## Warum es das gibt
Ox-Security hat (2026-05) offengelegt, dass über 200k MCP-stdio-Server
`child_process.exec` mit Template-Literalen umhüllen, die Benutzereingaben direkt aus
LLM-Tool-Argumenten tragen. LiteLLM v1.83.6 war das kanonische Beispiel (CVE in 1.83.7 gepatcht).
Dieses Bundle ist das defensive Gegenstück: ein Drop-in-Guard + Scanner,
der diese Klasse schließt. Inspiriert von AWS Linux `seccomp` + Chromium-Sandbox-Stufen.
## Siehe auch
- `HOOK_RECIPES.md` — Claude-Code-Hook-Rezepte, die gefährliche Tool-Aufrufe automatisch blockieren
- `CHANGELOG.md` — Release-Historie
- Ox-Security MCP-Audit: <https://venturebeat.com/security/200000-mcp-stdio-servers/>
- LiteLLM CVE-2026-XXXX: <https://github.com/BerriAI/litellm/security/advisories>
## Lizenz
MIT — Copyright (c) 2026 Matthias Meyer (StudioMeyer)