
mcp-stdio-shellguard v0.1.2
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.
Teil des StudioMeyer MCP Stack — Entwickelt auf Mallorca 🌴 · ⭐ wenn du es nutzt
mcp-stdio-shellguard
Defense-in-depth-Bundle für MCP-stdio-Server. Ummantelt `child_process.exec/spawn`mit Allowlist + Sandbox + Replay-Erkennung, plus ein AST-Audit-CLI (mcp-shellguard-audit),
das MCP-Serverquellen auf ungesäuberte Shell-Aufrufe scannt. Schließt die Ox-Security-
MCP-stdio-RCE-Klasse (200.000 verwundbare Server, Disclosure Mai 2026).
- MCP-Spezifikation: 2025-06-18
- SDK:
@modelcontextprotocol/sdk^1.29.0 - Node: >= 20
- Lizenz: MIT
- Autor: Matthias Meyer (StudioMeyer)
Installation
npm install mcp-stdio-shellguard
Oder das Audit-CLI direkt ohne Installation ausführen:
npx -y -p mcp-stdio-shellguard mcp-shellguard-audit scan ./src
Was du bekommst
Drei Ebenen, optional einzeln zuschaltbar:
- Bibliotheks-API — Drop-in-
guardExec/guardSpawn, die du aus deinem eigenen MCP-Server aufrufst. Default-Deny-Allowlist, Sandbox-Profile, Replay-Fenster. - Audit-CLI —
mcp-shellguard-audit scan <path>durchläuft den AST und meldet 12 Anti-Patterns von LOW (kein Timeout) bis CRITICAL (exec(\...${userInput}...`)`). - Referenz-MCP-Server —
mcp-stdio-shellguard-demostellt 8 Tools bereit, sodass MCP Inspector / Claude Desktop das Bundle direkt ansteuern können.
Tools (Referenzserver)
| Tool | Typ | Zweck |
|---|---|---|
guard_exec | destruktiv | Abgesichertes child_process.exec. Erzwingt den args[]-Vektor, Allowlist + Sandbox + Replay. Liefert stdout, stderr, exitCode, canonicalHash, isReplay, trustTier. |
guard_spawn | destruktiv | Abgesichertes child_process.spawn. Liefert SHA-256-Hashes von stdout/stderr statt der vollständigen Ausgaben. Lehnt shell:true hart ab. |
register_allowlist | mutierend | Registriert einen Toolnamen mit ausführbarer Datei + Argument-Regex. Ohne Registrierung gilt Default-Deny. |
audit_source | nur Lesen | Scannt einen TS/JS-Pfad auf Shell-Injection-Anti-Patterns. Liefert AuditFinding[] + Zusammenfassung. |
audit_report | nur Lesen | Formatiert ein Audit-Ergebnis als Markdown / JSON / SARIF 2.1.0. |
replay_check | nur Lesen | Berechnet den kanonischen SHA-256-Hash für einen Aufruf und meldet, ob er bereits im Replay-Fenster liegt. |
sandbox_status | nur Lesen | Meldet aktives Sandbox-Profil + konkrete Limits + cgroup-v2-Aktivflag. |
trust_tier | nur Lesen | Leitet die Stufe LOW/MEDIUM/HIGH/CRITICAL für ein registriertes Tool ab, inklusive Verbesserungshinweisen. |
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 per timeoutMs / fdBudget pro Aufruf verschärfen. Der Aufrufer kann
das Profil nicht erweitern.
Vertrauensstufen
| Stufe | Bedingung |
|---|---|
| LOW | Tool nicht registriert (Default-Deny) |
| MEDIUM | registriert, aber argsPatterns leer (beliebige Argumente erlaubt) |
| HIGH | argsPatterns gesetzt, aber Sandbox oder Replay-Tracker inaktiv |
| CRITICAL | argsPatterns + Sandbox + Replay alle aktiv |
Anheben von LOW → CRITICAL durch Registrieren des Tools + Setzen von argsPatterns + Ausführen
über guardExec/guardSpawn (die Sandbox + Replay immer aktivieren).
Bibliotheks-Schnellstart
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
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:
0sauber (keine Befunde auf oder über der Schwelle)1Befunde vorhanden2Parse-/IO-Fehler
Anti-Pattern-Bibliothek (12 Regeln)
| ID | Schweregrad | Auslöser |
|---|---|---|
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-Bindungen auf, bevor er abgleicht,
sodass die gefährlichen Formen unten auch dann erkannt werden, wenn der Aufruf über
einen Alias läuft statt über ein literales child_process.exec:
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 sich ihre asynchronen Regeln,
und shell_true_option feuert auch bei einem String-Shell ({ shell: "/bin/sh" })
oder einem dynamischen Shell-Wert — 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).
Pragma-Kommentare
// shellguard:ignore-next-line— einen Befund unterdrücken// shellguard:ignore-file— ganze Datei unterdrücken (selten; besser zeilenweise)
Warum es das gibt
Ox-Security hat (2026-05) offengelegt, dass über 200.000 MCP-stdio-Server
child_process.exec mit Template-Literalen umhüllen, die Benutzereingaben direkt aus
LLM-Tool-Argumenten übernehmen. LiteLLM v1.83.6 war das kanonische Beispiel (CVE in 1.83.7 gepatcht).
Dieses Bundle ist das defensive Sicherheits-Pendant: ein Drop-in-Wächter + Scanner,
der die 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 blockierenCHANGELOG.md— Versionshistorie- 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)