MCP stdio 服务器的纵深防御包:即插即用的 guardExec/guardSpawn 包装器、AST 审计 CLI、参考 MCP 服务器。修复了 Ox-Security 20 万服务器 stdio-RCE 类漏洞(LiteLLM CVE-2025-69256)。MIT 许可,TypeScript,Node >= 20。
StudioMeyer MCP Stack 的一部分 — 在馬略卡島打造 🌴 · 如果你覺得有用,請給個 ⭐
@modelcontextprotocol/sdk ^1.29.0npm install mcp-stdio-shellguard
或者直接运行审计 CLI,无需安装:
npx -y -p mcp-stdio-shellguard mcp-shellguard-audit scan ./src
三个层次,可按需逐层启用:
guardExec / guardSpawn,从你自己的 MCP 服务器中调用。默认拒绝的允许列表、沙箱配置文件、重放窗口。mcp-shellguard-audit scan <path> 遍历 AST,报告 12 种反模式,从 LOW(no timeout)到 CRITICAL(exec(\...${userInput}...`)`)。mcp-stdio-shellguard-demo 暴露 8 个工具,以便 MCP Inspector / Claude Desktop 可以直接驱动该捆绑包。| 工具 | 类型 | 用途 |
|---|---|---|
guard_exec | 破坏性 | 受防护的 child_process.exec。强制使用 args[] 向量、允许列表 + 沙箱 + 重放。返回 stdout、stderr、exitCode、canonicalHash、isReplay、trustTier。 |
guard_spawn | 破坏性 | 受防护的 child_process.spawn。返回 stdout/stderr 的 SHA-256 哈希,而非完整内容。硬性拒绝 shell:true。 |
register_allowlist | 变更性 | 注册工具名称及其可执行文件 + 参数正则表达式。未注册则适用默认拒绝。 |
audit_source | 只读 | 扫描 TS/JS 路径以查找 shell 注入反模式。返回 AuditFinding[] + 摘要。 |
audit_report | 只读 | 将审计结果格式化为 markdown / json / SARIF 2.1.0。 |
replay_check | 只读 | 计算一次调用的规范 SHA-256 哈希,并报告其是否已在重放窗口中。 |
sandbox_status | 只读 | 报告活动沙箱配置文件 + 具体限制 + cgroup-v2 活动标志。 |
trust_tier | 只读 | 为已注册工具推导 LOW/MEDIUM/HIGH/CRITICAL 等级,并提供改进提示。 |
| 配置文件 | 超时 | 最大 stdout | 最大 stderr | FD 预算 | cgroup-v2 |
|---|---|---|---|---|---|
strict | 5 秒 | 1 MB | 256 KB | 32 | 是(cpu/memory) |
standard(默认) | 30 秒 | 10 MB | 1 MB | 256 | 是 |
permissive | 5 分钟 | 100 MB | 10 MB | 1024 | 否 |
调用方可通过每次调用的 timeoutMs / fdBudget 收紧限制。调用方不能放宽超出配置文件的范围。
| 等级 | 条件 |
|---|---|
| LOW | 工具未注册(默认拒绝) |
| MEDIUM | 已注册但 argsPatterns 为空(允许任意参数) |
| HIGH | 已设置 argsPatterns 但沙箱或重放跟踪器未激活 |
| CRITICAL | argsPatterns + 沙箱 + 重放全部激活 |
通过注册工具 + 设置 argsPatterns + 通过 guardExec/guardSpawn 运行(它们始终激活沙箱 + 重放),可将 LOW 提升至 CRITICAL。
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
退出码:
0 干净(没有达到或超过下限的发现项)1 存在发现项2 解析 / IO 错误传递给 scanPath 或 audit_source MCP 工具的自定义 ignoreGlobs 最多接受
128 个字符串。每个字符串限制为 1024 个 UTF-16 代码单元和 16
个未转义的起始分组字符((、{、[)。超过这些
预算的输入会在匹配前引发受控错误。
| ID | 严重性 | 触发条件 |
|---|---|---|
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 | 没有 maxBuffer 的 exec |
missing_timeout | LOW | 没有 timeout 的 exec/spawn |
扫描器在匹配前会解析重命名的 child_process 绑定,
因此即使调用通过别名而非字面量 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(...)同步变体(spawnSync、execFileSync)共享其异步规则,
并且 shell_true_option 也会在字符串 shell({ shell: "/bin/sh" })
或动态 shell 值上触发——而不仅仅是字面量 { shell: true }。对非
child_process 函数的 promisify、从另一个模块解构,以及
{ shell: false } 保持干净(无误报)。
// shellguard:ignore-next-line — 抑制一个发现项// shellguard:ignore-file — 抑制整个文件(罕见;优先按行)Ox-Security 披露(2026-05),200k+ MCP stdio 服务器用携带直接来自
LLM 工具参数的用户输入的模板字面量包装
child_process.exec。LiteLLM v1.83.6 是典型示例(CVE 已在 1.83.7 中修补)。
此捆绑包是防御性安全的对应物:一个即插即用的防护 + 扫描器
来关闭这一类问题。灵感来自 AWS Linux seccomp + Chromium 沙箱层级。
HOOK_RECIPES.md — 自动阻止危险工具调用的 Claude Code 钩子配方CHANGELOG.md — 发布历史MIT — Copyright (c) 2026 Matthias Meyer (StudioMeyer)