Retour aux mises à jour
New releaseAug 21, 2026

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.

Partager

Fait partie de la StudioMeyer MCP Stack — Conçu à Majorque 🌴 · ⭐ si vous l'utilisez

mcp-stdio-shellguard

npm version npm downloads License Last commit GitHub stars

Bundle de défense en profondeur pour les serveurs MCP stdio. Enveloppe `child_process.exec/spawn`

avec allowlist + sandbox + détection de rejeu, plus une CLI d'audit AST (mcp-shellguard-audit) qui analyse les sources des serveurs MCP à la recherche d'appels shell non assainis. Comble la classe de RCE stdio MCP d'Ox-Security (200k serveurs vulnérables, divulgation de mai 2026).

  • Spécification MCP : 2025-06-18
  • SDK : @modelcontextprotocol/sdk ^1.29.0
  • Node : >= 20
  • Licence : MIT
  • Auteur : Matthias Meyer (StudioMeyer)

Installation

npm install mcp-stdio-shellguard

Ou exécutez directement la CLI d'audit sans installation :

npx -y -p mcp-stdio-shellguard mcp-shellguard-audit scan ./src

Ce que cela vous apporte

Trois couches, activables individuellement :

  1. API de bibliothèque — guardExec / guardSpawn prêts à l'emploi que vous appelez depuis votre propre serveur MCP. Liste d'autorisation par défaut en refus, profils de bac à sable, fenêtre de rejeu.
  2. CLI d'audit — mcp-shellguard-audit scan <path> parcourt l'AST, signale 12 anti-patterns allant de LOW (no timeout) à CRITICAL (exec(\...${userInput}...`)`).
  3. Serveur MCP de référence — mcp-stdio-shellguard-demo expose 8 outils afin que le MCP Inspector / Claude Desktop puissent piloter le bundle directement.

Outils (serveur de référence)

OutilTypeObjectif
guard_execdestructifchild_process.exec protégé. Force le vecteur args[], liste d'autorisation + bac à sable + rejeu. Renvoie stdout, stderr, exitCode, canonicalHash, isReplay, trustTier.
guard_spawndestructifchild_process.spawn protégé. Renvoie les hachages SHA-256 de stdout/stderr au lieu des corps complets. Rejette fermement shell:true.
register_allowlistmutationEnregistre un nom d'outil avec un exécutable + une regex d'arguments. Sans enregistrement, le refus par défaut s'applique.
audit_sourcelecture seuleAnalyse un chemin TS/JS à la recherche d'anti-patterns d'injection de shell. Renvoie AuditFinding[] + un résumé.
audit_reportlecture seuleFormate un résultat d'audit en markdown / json / SARIF 2.1.0.
replay_checklecture seuleCalcule le hachage SHA-256 canonique d'une invocation et indique s'il figure déjà dans la fenêtre de rejeu.
sandbox_statuslecture seuleIndique le profil de bac à sable actif + les limites concrètes + l'indicateur d'activation cgroup-v2.
trust_tierlecture seuleDéduit le niveau LOW/MEDIUM/HIGH/CRITICAL d'un outil enregistré ainsi que des conseils d'amélioration.

Profils de bac à sable

ProfilDélai d'expirationstdout maxstderr maxBudget de FDcgroup-v2
strict5 s1 Mo256 Ko32oui (cpu/mémoire)
standard (par défaut)30 s10 Mo1 Mo256oui
permissive5 min100 Mo10 Mo1024non

L'appelant peut resserrer via timeoutMs / fdBudget à chaque appel. L'appelant ne peut pas élargir au-delà du profil.

Niveaux de confiance

NiveauCondition
LOWoutil non enregistré (refus par défaut)
MEDIUMenregistré mais argsPatterns vide (tous les arguments autorisés)
HIGHargsPatterns défini mais bac à sable ou traqueur de rejeu inactif
CRITICALargsPatterns + bac à sable + rejeu tous actifs

Passez de LOW → CRITICAL en enregistrant l'outil + en définissant argsPatterns + en passant par guardExec/guardSpawn (qui activent toujours le bac à sable + le rejeu).

Démarrage 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

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

Codes de sortie :

  • 0 propre (aucune détection au niveau plancher ou au-dessus)
  • 1 détections présentes
  • 2 erreurs d'analyse / d'E/S

Les ignoreGlobs personnalisés transmis à scanPath ou à l'outil MCP audit_source acceptent au maximum 128 chaînes. Chaque chaîne est limitée à 1024 unités de code UTF-16 et à 16 caractères d'ouverture de groupe non échappés ((, {, [). Les entrées dépassant ces budgets déclenchent une erreur contrôlée avant la mise en correspondance.

Bibliothèque d'anti-patterns (12 règles)

IDSévéritéDéclenché par
exec_template_literal_with_inputCRITICALchild_process.exec(\ls ${x}`)`
exec_dynamic_stringCRITICALchild_process.exec(cmd)
exec_sync_dynamic_stringCRITICALchild_process.execSync(cmd)
eval_near_child_processCRITICALeval(...)
function_constructor_near_child_processCRITICALnew Function(...)
spawn_dynamic_file_argsHIGHspawn(bin, userArgs)
exec_file_dynamicHIGHexecFile(bin, ...)
shell_true_optionHIGH{ shell: true }
os_system_equivalentHIGHDeno.run / Bun.spawn
spawn_literal_dynamic_argsMEDIUMspawn('git', userArgs)
unbounded_bufferLOWexec sans maxBuffer
missing_timeoutLOWexec/spawn sans timeout

Le scanner résout les liaisons child_process renommées avant la mise en 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 littéral 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(...)

Les variantes synchrones (spawnSync, execFileSync) partagent leurs règles asynchrones, et shell_true_option se déclenche également sur un shell sous forme de chaîne ({ shell: "/bin/sh" }) ou sur une valeur de shell dynamique — pas seulement sur le littéral { shell: true }. Un promisify d'une fonction non liée à child_process, une déstructuration depuis un autre module, et { shell: false } restent propres (aucun faux positif).

Pragmas

Catégories