
MCP stdio 서버용 심층 방어 번들: 드롭인 guardExec/guardSpawn 래퍼, AST 감사 CLI, 레퍼런스 MCP 서버. Ox-Security 200k 서버 stdio-RCE 클래스(LiteLLM CVE-2025-69256)를 차단합니다. MIT, TypeScript, Node >= 20.
**StudioMeyer MCP Stack**의 일부 — Mallorca에서 제작 🌴 · 사용하신다면 ⭐을 눌러주세요
허용 목록(allowlist) + 샌드박스 + 재생 탐지(replay-detection)로 감싸며, 추가로 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
세 개의 계층으로 구성되며, 선택적으로 부분 적용할 수 있습니다:
guardExec / guardSpawn. 기본 거부(default-deny) 허용 목록, 샌드박스 프로필, 재생 윈도우.mcp-shellguard-audit scan <path>는 AST를 순회하며 LOW(no timeout)부터 CRITICAL(exec(\...${userInput}...`)`)까지 12가지 안티패턴을 보고합니다.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 | 변경 | 도구 이름을 실행 파일 + args 정규식과 함께 등록합니다. 등록하지 않으면 기본 거부가 적용됩니다. |
audit_source | 읽기 전용 | TS/JS 경로에서 셸 인젝션 안티패턴을 스캔합니다. 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 s | 1 MB | 256 KB | 32 | 예 (cpu/memory) |
standard (기본) | 30 s | 10 MB | 1 MB | 256 | 예 |
permissive | 5 min | 100 MB | 10 MB | 1024 | 아니오 |
호출자는 호출별로 timeoutMs / fdBudget을 통해 제한을 강화할 수 있습니다. 호출자는 프로필을 넘어 확장할 수 없습니다.
| 등급 | 조건 |
|---|---|
| LOW | 도구 미등록 (기본 거부) |
| MEDIUM | 등록되었으나 argsPatterns 비어 있음 (모든 args 허용) |
| 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 — 발견 사항 하나 억제// 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)