
mcp-stdio-shellguard v0.1.2
Bundle de défense en profondeur pour serveurs MCP stdio : wrappers guardExec/guardSpawn prêts à l'emploi, CLI d'audit AST, serveur MCP de référence. Corrige la classe de vulnérabilité stdio-RCE Ox-Security 200k-server (LiteLLM CVE-2025-69256). MIT, TypeScript, Node >= 20.
Partie de la pile MCP StudioMeyer — Construit à Majorque 🌴 · ⭐ si vous l'utilisez
mcp-stdio-shellguard
Bundle de défense en profondeur pour les serveurs MCP stdio. Enveloppe `child_process.exec/spawn`avec allowlist + sandbox + détection de rejeu, plus un CLI d'audit AST (mcp-shellguard-audit)
qui analyse les sources des serveurs MCP pour les appels shell non nettoyés. Ferme la classe
Ox-Security MCP stdio-RCE (200 000 serveurs vulnérables, divulgation de mai 2026).
- MCP spec : 2025-06-18
- SDK :
@modelcontextprotocol/sdk^1.29.0 - Node : >= 20
- License : MIT
- Author : Matthias Meyer (StudioMeyer)
Installer
npm install mcp-stdio-shellguard
Ou exécutez le CLI d'audit directement sans installer :
npx -y -p mcp-stdio-shellguard mcp-shellguard-audit scan ./src
Ce qu'il vous apporte
Trois couches, opt-in par morceaux :
- API de bibliothèque —
guardExec/guardSpawnprêts à l'emploi que vous appelez depuis votre propre serveur MCP. Allowlist par défaut de refus, profils de sandbox, fenêtre de rejeu. - CLI d'audit —
mcp-shellguard-audit scan <path>parcourt l'AST, rapporte 12 anti-patrons de LOW (no timeout) à CRITICAL (exec(\...${userInput}...`)`). - Serveur MCP de référence —
mcp-stdio-shellguard-demoexpose 8 outils pour que l'Inspecteur MCP / Claude Desktop puisse piloter le bundle directement.
Outils (serveur de référence)
| Outil | Type | Objectif |
|---|---|---|
guard_exec | destructif | Exécution protégée de child_process.exec. Force le vecteur args[], allowlist + sandbox + rejeu. Retourne stdout, stderr, exitCode, canonicalHash, isReplay, trustTier. |
guard_spawn | destructif | Exécution protégée de child_process.spawn. Retourne les hachages SHA-256 de stdout/stderr au lieu des corps complets. Rejette strictement shell:true. |
register_allowlist | mutateur | Enregistrer un nom d'outil avec exécutable + regex d'args. Sans enregistrement, le refus par défaut s'applique. |
audit_source | lecture seule | Analyser un chemin TS/JS pour les anti-patrons d'injection shell. Retourne AuditFinding[] + résumé. |
audit_report | lecture seule | Formater un résultat d'audit en markdown / json / SARIF 2.1.0. |
replay_check | lecture seule | Calculer le hachage SHA-256 canonique pour une invocation et signaler s'il est déjà dans la fenêtre de rejeu. |
sandbox_status | lecture seule | Signaler le profil de sandbox actif + limites concrètes + indicateur cgroup-v2 actif. |
trust_tier | lecture seule | Dériver le niveau LOW/MEDIUM/HIGH/CRITICAL pour un outil enregistré plus des conseils d'amélioration. |
Profils de sandbox
| Profil | Timeout | Max stdout | Max stderr | Budget FD | cgroup-v2 |
|---|---|---|---|---|---|
strict | 5 s | 1 MB | 256 KB | 32 | oui (cpu/memory) |
standard (par défaut) | 30 s | 10 MB | 1 MB | 256 | oui |
permissif | 5 min | 100 MB | 10 MB | 1024 | non |
L'appelant peut resserrer via timeoutMs / fdBudget par appel. L'appelant ne peut pas élargir au-delà du profil.
Niveaux de confiance
| Niveau | Condition |
|---|---|
| LOW | outil non enregistré (refus par défaut) |
| MEDIUM | enregistré mais argsPatterns vide (tous les args autorisés) |
| HIGH | argsPatterns défini mais sandbox ou suivi de rejeu inactif |
| CRITICAL | argsPatterns + sandbox + rejeu tous actifs |
Passez de LOW à CRITICAL en enregistrant l'outil + en définissant argsPatterns + en passant par guardExec/guardSpawn (qui activent toujours sandbox + rejeu).
Guide rapide de la bibliothèque
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
CLI d'audit
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
Codes de sortie :
0propre (aucun résultat au niveau ou au-dessus du seuil)1résultats présents2erreurs d'analyse / E/S
Bibliothèque d'anti-patrons (12 règles)
| ID | Sévérité | Déclenché sur |
|---|---|---|
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 sans maxBuffer |
missing_timeout | LOW | exec/spawn sans timeout |
Le scanner résout les liaisons child_process renommées avant la correspondance, de sorte que les formes dangereuses ci-dessous sont détectées même lorsque l'appel passe par un alias plutôt que par un child_process.exec littéral :
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(...)
Les variantes synchrones (spawnSync, execFileSync) partagent leurs règles asynchrones, et shell_true_option se déclenche également sur un shell chaîne ({ shell: "/bin/sh" }) ou une valeur de shell dynamique — pas seulement sur le littéral { shell: true }. Un promisify d'une fonction non-child_process, une déstructuration d'un autre module et { shell: false } restent propres (pas de faux positifs).
Pragmas
// shellguard:ignore-next-line— supprimer un résultat// shellguard:ignore-file— supprimer tout le fichier (rare ; préférer par ligne)
Pourquoi cela existe
Ox-Security a divulgué (2026-05) que plus de 200 000 serveurs MCP stdio enveloppent child_process.exec avec des littéraux de gabarit transportant des entrées utilisateur directement depuis les arguments d'outils LLM. LiteLLM v1.83.6 était l'exemple canonique (CVE corrigé dans 1.83.7). Ce bundle est le pendant défensif de sécurité : un garde-fou prêt à l'emploi + un scanner qui ferme la classe. Inspiré par AWS Linux seccomp + les niveaux de sandbox Chromium.
Voir aussi
HOOK_RECIPES.md— recettes de hook Claude Code qui bloquent automatiquement les appels d'outils dangereuxCHANGELOG.md— historique des versions- Audit MCP d'Ox-Security : https://venturebeat.com/security/200000-mcp-stdio-servers/
- LiteLLM CVE-2026-XXXX : https://github.com/BerriAI/litellm/security/advisories
Licence
MIT — Copyright (c) 2026 Matthias Meyer (StudioMeyer)