
MCP stdio サーバー向け多層防御バンドル: そのまま使える guardExec/guardSpawn ラッパー、AST 監査 CLI、リファレンス MCP サーバー。Ox-Security の 20 万サーバー規模の stdio-RCE クラス(LiteLLM CVE-2025-69256)を解消。MIT ライセンス、TypeScript、Node >= 20。
StudioMeyer MCP Stack の一部 — マヨルカ島で構築 🌴 · 役に立ったら ⭐ を
許可リスト + サンドボックス + リプレイ検出でラップし、さらに未サニタイズのシェル呼び出しを
MCP サーバーのソースからスキャンする AST 監査 CLI(mcp-shellguard-audit)を提供します。
Ox-Security の MCP stdio-RCE クラス(2026年5月公表、20万台の脆弱なサーバー)を封じ込めます。
@modelcontextprotocol/sdk ^1.29.0npm install mcp-stdio-shellguard
または、インストールせずに監査CLIを直接実行します:
npx -y -p mcp-stdio-shellguard mcp-shellguard-audit scan ./src
3つのレイヤーを段階的にオプトインできます:
guardExec / guardSpawn。デフォルト拒否の許可リスト、サンドボックスプロファイル、リプレイウィンドウ。mcp-shellguard-audit scan <path> が AST を走査し、LOW(no timeout)から CRITICAL(exec(\...${userInput}...`)`)までの12種類のアンチパターンを報告します。mcp-stdio-shellguard-demo が8つのツールを公開し、MCP Inspector / Claude Desktop からバンドルを直接操作できます。| ツール | 種別 | 目的 |
|---|---|---|
guard_exec | destructive | 防御された child_process.exec。args[] ベクター、許可リスト + サンドボックス + リプレイを強制。stdout、stderr、exitCode、canonicalHash、isReplay、trustTier を返します。 |
guard_spawn | destructive | 防御された child_process.spawn。完全なボディの代わりに stdout/stderr の SHA-256 ハッシュを返します。shell:true を厳格に拒否します。 |
register_allowlist | mutating | ツール名を実行ファイル + args 正規表現とともに登録します。登録がない場合はデフォルト拒否が適用されます。 |
audit_source | read-only | TS/JS パスをスキャンしてシェルインジェクションのアンチパターンを検出します。AuditFinding[] + サマリーを返します。 |
audit_report | read-only | 監査結果を markdown / json / SARIF 2.1.0 としてフォーマットします。 |
replay_check | read-only | 呼び出しの正規 SHA-256 ハッシュを計算し、すでにリプレイウィンドウ内にあるかどうかを報告します。 |
sandbox_status | read-only | アクティブなサンドボックスプロファイル + 具体的な制限 + cgroup-v2 アクティブフラグを報告します。 |
trust_tier | read-only | 登録済みツールの 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: true } だけでなく、文字列の
シェル({ shell: "/bin/sh" })や動的なシェル値でも発火します。
child_process 以外の関数に対する promisify、別モジュールからの分割代入、
および { shell: false } はクリーンのままです(誤検出なし)。
// shellguard:ignore-next-line — 1 件の検出を抑制// shellguard:ignore-file — ファイル全体を抑制(まれ。行単位を推奨)Ox-Security は(2026-05 に)、20 万以上の 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)