
mcp-stdio-shellguard v0.1.2
Paquete de defensa en profundidad para servidores MCP stdio: wrappers guardExec/guardSpawn de sustitución directa, CLI de auditoría AST, servidor MCP de referencia. Cierra la clase stdio-RCE de los 200 000 servidores de Ox-Security (LiteLLM CVE-2025-69256). MIT, TypeScript, Node >= 20.
Parte del StudioMeyer MCP Stack — Hecho en Mallorca 🌴 · ⭐ si lo usas
mcp-stdio-shellguard
Paquete de defensa en profundidad para servidores MCP stdio. Envuelve child_process.exec/spawn con lista blanca + sandbox + detección de replay, además de una CLI de auditoría AST (mcp-shellguard-audit) que escanea el código fuente de servidores MCP en busca de llamadas shell sin sanitizar. Cierra la clase de vulnerabilidad MCP stdio-RCE señalada por Ox-Security (200k servidores vulnerables, divulgación de mayo de 2026).
- Especificación MCP: 2025-06-18
- SDK:
@modelcontextprotocol/sdk^1.29.0 - Node: >= 20
- Licencia: MIT
- Autor: Matthias Meyer (StudioMeyer)
Instalación
npm install mcp-stdio-shellguard
O ejecuta la CLI de auditoría directamente sin instalar:
npx -y -p mcp-stdio-shellguard mcp-shellguard-audit scan ./src
Qué te ofrece
Tres capas, opt-in por piezas:
- API de librería —
guardExec/guardSpawncomo reemplazo directo que puedes invocar desde tu propio servidor MCP. Lista blanca de denegación por defecto, perfiles de sandbox y ventana de replay. - CLI de auditoría —
mcp-shellguard-audit scan <path>recorre el AST e informa de 12 antipatrones, desde LOW (sin timeout) hasta CRITICAL (exec(\...${userInput}...`)`). - Servidor MCP de referencia —
mcp-stdio-shellguard-demoexpone 8 herramientas para que MCP Inspector / Claude Desktop puedan manejar el paquete directamente.
Herramientas (servidor de referencia)
| Herramienta | Tipo | Propósito |
|---|---|---|
guard_exec | destructivo | child_process.exec defendido. Fuerza el vector args[], lista blanca + sandbox + replay. Devuelve stdout, stderr, exitCode, canonicalHash, isReplay, trustTier. |
guard_spawn | destructivo | child_process.spawn defendido. Devuelve hashes SHA-256 de stdout/stderr en lugar de los cuerpos completos. Rechaza de forma tajante shell:true. |
register_allowlist | mutador | Registra un nombre de herramienta con ejecutable + regex de argumentos. Sin registro, se aplica la denegación por defecto. |
audit_source | solo lectura | Escanea una ruta TS/JS en busca de antipatrones de inyección shell. Devuelve AuditFinding[] + resumen. |
audit_report | solo lectura | Formatea un resultado de auditoría como markdown / json / SARIF 2.1.0. |
replay_check | solo lectura | Calcula el hash SHA-256 canónico de una invocación e informa de si ya está en la ventana de replay. |
sandbox_status | solo lectura | Informa del perfil de sandbox activo, los límites concretos y el indicador de cgroup-v2 activo. |
trust_tier | solo lectura | Deriva el nivel LOW/MEDIUM/HIGH/CRITICAL para una herramienta registrada, además de sugerencias de mejora. |
Perfiles de sandbox
| Perfil | Timeout | Máx. stdout | Máx. stderr | Presupuesto de FD | cgroup-v2 |
|---|---|---|---|---|---|
strict | 5 s | 1 MB | 256 KB | 32 | sí (cpu/memoria) |
standard (predeterminado) | 30 s | 10 MB | 1 MB | 256 | sí |
permissive | 5 min | 100 MB | 10 MB | 1024 | no |
Quien llama puede ajustar a la baja mediante timeoutMs / fdBudget por llamada, pero no puede ampliar los límites más allá del perfil.
Niveles de confianza
| Nivel | Condición |
|---|---|
| LOW | herramienta no registrada (denegación por defecto) |
| MEDIUM | registrada pero con argsPatterns vacío (se permiten argumentos arbitrarios) |
| HIGH | argsPatterns definido pero con sandbox o rastreador de replay inactivos |
| CRITICAL | argsPatterns + sandbox + replay todos activos |
Sube de LOW → CRITICAL registrando la herramienta, definiendo argsPatterns y ejecutándola a través de guardExec/guardSpawn (que siempre activan sandbox + replay).
Inicio rápido con la librería
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 de auditoría
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
Códigos de salida:
0limpio (sin hallazgos en o por encima del umbral)1hay hallazgos2errores de análisis / E/S
Biblioteca de antipatrones (12 reglas)
| ID | Gravedad | Se dispara con |
|---|---|---|
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 sin maxBuffer |
missing_timeout | LOW | exec/spawn sin timeout |
El escáner resuelve los enlaces renombrados de child_process antes de comparar, por lo que las formas peligrosas siguientes se detectan incluso cuando la llamada pasa por un alias en lugar de un child_process.exec literal:
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(...)
Las variantes síncronas (spawnSync, execFileSync) comparten sus reglas asíncronas, y shell_true_option también se activa con un shell en forma de cadena ({ shell: "/bin/sh" }) o con un valor de shell dinámico — no solo con el literal { shell: true }. Un promisify de una función que no sea de child_process, una desestructuración de otro módulo y { shell: false } permanecen limpios (sin falsos positivos).
Pragmas
// shellguard:ignore-next-line— suprime un hallazgo// shellguard:ignore-file— suprime el archivo completo (poco habitual; prefiere la opción por línea)
Por qué existe
Ox-Security reveló (2026-05) que más de 200k servidores MCP stdio envuelven child_process.exec con template literals que llevan entrada del usuario directamente desde los argumentos de las herramientas del LLM. LiteLLM v1.83.6 fue el ejemplo canónico (CVE parcheado en 1.83.7). Este paquete es la contrapartida de seguridad defensiva: un guardián de sustitución directa + escáner que cierra esta clase de vulnerabilidad. Inspirado en seccomp de AWS Linux y en los niveles de sandbox de Chromium.
Ver también
HOOK_RECIPES.md— recetas de hooks para Claude Code que bloquean automáticamente llamadas peligrosas a herramientasCHANGELOG.md— historial de versiones- Auditoría MCP de Ox-Security: https://venturebeat.com/security/200000-mcp-stdio-servers/
- CVE-2026-XXXX de LiteLLM: https://github.com/BerriAI/litellm/security/advisories
Licencia
MIT — Copyright (c) 2026 Matthias Meyer (StudioMeyer)