
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
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).
@modelcontextprotocol/sdk ^1.29.0npm 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
Trois couches, opt-in par morceaux :
guardExec / guardSpawn prêts à l'emploi que vous appelez depuis votre propre serveur MCP. Allowlist par défaut de refus, profils de sandbox, fenêtre de rejeu.mcp-shellguard-audit scan <path> parcourt l'AST, rapporte 12 anti-patrons de LOW (no timeout) à CRITICAL (exec(\...${userInput}...`)`).mcp-stdio-shellguard-demo expose 8 outils pour que l'Inspecteur MCP / Claude Desktop puisse piloter le bundle directement.L'appelant peut resserrer via timeoutMs / fdBudget par appel. L'appelant ne peut pas élargir au-delà du profil.
| Niveau | Condition |
|---|---|
| LOW | outil non enregistré (refus par défaut) |
| MEDIUM | enregistré mais argsPatterns vide (tous les args autorisés) |
Passez de LOW à CRITICAL en enregistrant l'outil + en définissant argsPatterns + en passant par guardExec/guardSpawn (qui activent toujours sandbox + rejeu).
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
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 :
0 propre (aucun résultat au niveau ou au-dessus du seuil)1 résultats présents2 erreurs d'analyse / E/SLe 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).
// shellguard:ignore-next-line — supprimer un résultat// shellguard:ignore-file — supprimer tout le fichier (rare ; préférer par ligne)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.
HOOK_RECIPES.md — recettes de hook Claude Code qui bloquent automatiquement les appels d'outils dangereuxCHANGELOG.md — historique des versionsMIT — Copyright (c) 2026 Matthias Meyer (StudioMeyer)
| 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. |
| 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 |
| HIGH | argsPatterns défini mais sandbox ou suivi de rejeu inactif |
| CRITICAL | argsPatterns + sandbox + rejeu tous actifs |
| 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 |