
حزمة دفاع متعدد الطبقات لخوادم MCP stdio: أغلفة guardExec/guardSpawn جاهزة للتبديل المباشر (drop-in)، وأداة CLI لتدقيق AST، وخادم MCP مرجعي. تسد فئة ثغرة stdio-RCE الخاصة بـ Ox-Security التي تمس 200 ألف خادم (LiteLLM CVE-2025-69256). MIT، TypeScript، Node >= 20.
جزء من StudioMeyer MCP Stack — صُنع في مايوركا 🌴 · ⭐ إذا استخدمته
بقائمة سماح + صندوق رمل + كشف إعادة التشغيل، إضافةً إلى أداة تدقيق AST عبر سطر الأوامر (mcp-shellguard-audit)
تفحص مصادر خادم MCP بحثًا عن استدعاءات shell غير المعقَّمة. تُغلق فئة Ox-Security
MCP stdio-RCE (200 ألف خادم معرَّض للخطر، إفصاح مايو 2026).
@modelcontextprotocol/sdk ^1.29.0npm install mcp-stdio-shellguard
أو شغّل واجهة سطر أوامر التدقيق مباشرةً دون تثبيت:
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 محمية. تُرجع تجزئات SHA-256 لـ stdout/stderr بدلاً من المحتوى الكامل. ترفض shell:true بشكل صارم. |
register_allowlist | معدّلة | تسجيل اسم أداة مع ملف تنفيذي + تعبير نمطي للوسائط. بدون التسجيل يُطبّق الرفض الافتراضي. |
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 ثوانٍ | 1 ميغابايت | 256 كيلوبايت | 32 | نعم (cpu/memory) |
standard (افتراضي) | 30 ثانية | 10 ميغابايت | 1 ميغابايت | 256 | نعم |
permissive | 5 دقائق | 100 ميغابايت | 10 ميغابايت | 1024 | لا |
يمكن للمستدعي التشديد عبر timeoutMs / fdBudget لكل استدعاء. لا يمكن للمستدعي التوسيع
بما يتجاوز الملف التعريفي.
| المستوى | الشرط |
|---|---|
| LOW | الأداة غير مسجّلة (الرفض الافتراضي) |
| MEDIUM | مسجّلة لكن argsPatterns فارغة (أي وسائط مسموح بها) |
| HIGH | argsPatterns محددة لكن العزل أو متتبع إعادة التشغيل غير نشط |
| CRITICAL | argsPatterns + العزل + إعادة التشغيل جميعها نشطة |
ارفع من LOW → CRITICAL بتسجيل الأداة + تعيين argsPatterns + التشغيل
عبر guardExec/guardSpawn (التي تفعّل دائمًا العزل + إعادة التشغيل).
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 أخطاء تحليل / إدخال وإخراجتقبل ignoreGlobs المخصصة التي تُمرَّر إلى scanPath أو أداة MCP audit_source
128 سلسلة نصية كحد أقصى. كل سلسلة محدودة بـ 1024 وحدة من وحدات UTF-16 و16
حرف تجميع افتتاحي غير مهرَّب ((, {, [). المدخلات التي تتجاوز هذه
الميزانيات تُطلق خطأً خاضعاً للتحكم قبل المطابقة.
| المعرّف | الخطورة | يُفعَّل عند |
|---|---|---|
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 بدون maxBuffer |
missing_timeout | LOW | exec/spawn بدون timeout |
يحل الماسح الارتباطات المُعاد تسميتها لـ 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 } الحرفي. يبقى
promisify لدالة ليست من child_process، والتفكيك من وحدة أخرى،
و{ shell: false } نظيفاً (دون نتائج إيجابية خاطئة).
// shellguard:ignore-next-line — كتم نتيجة واحدة// shellguard:ignore-file — كتم الملف بأكمله (نادر؛ يُفضَّل لكل سطر)أفصحت Ox-Security (2026-05) أن أكثر من 200 ألف خادم MCP stdio يغلّفون
child_process.exec بقوالب نصية تحمل مدخلات المستخدم مباشرة من
وسائط أدوات LLM. كان LiteLLM v1.83.6 المثال النموذجي (تم ترقيع CVE في 1.83.7).
هذه الحزمة هي النظير الأمني الدفاعي: حارس + ماسح جاهز للاستخدام
يسدّ هذه الفئة. مستوحى من seccomp في AWS Linux + مستويات sandbox في Chromium.