MCP stdio सर्वरों के लिए गहन सुरक्षा बंडल: ड्रॉप-इन guardExec/guardSpawn रैपर, AST ऑडिट CLI, संदर्भ MCP सर्वर। Ox-Security 200k-server stdio-RCE श्रेणी (LiteLLM CVE-2025-69256) को समाप्त करता है। MIT, TypeScript, Node >= 20।
StudioMeyer MCP Stack का हिस्सा — Mallorca 🌴 में निर्मित · ⭐ अगर आप इसका उपयोग करते हैं
@modelcontextprotocol/sdk ^1.29.0npm install mcp-stdio-shellguard
या सीधे इंस्टॉल किए बिना ऑडिट CLI चलाएँ:
npx -y -p mcp-stdio-shellguard mcp-shellguard-audit scan ./src
तीन परतें, चरणबद्ध रूप से opt-in:
guardExec / guardSpawn जिन्हें आप अपने
स्वयं के MCP सर्वर से कॉल करते हैं। डिफ़ॉल्ट-अस्वीकार allowlist, सैंडबॉक्स प्रोफ़ाइल, रीप्ले विंडो।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[] वेक्टर, allowlist + सैंडबॉक्स + रीप्ले को बाध्य करता है। stdout, stderr, exitCode, canonicalHash, isReplay, trustTier लौटाता है। |
guard_spawn | विनाशकारी | सुरक्षित child_process.spawn। पूर्ण बॉडी के बजाय stdout/stderr के SHA-256 हैश लौटाता है। shell:true को सख्ती से अस्वीकार करता है। |
register_allowlist | परिवर्तनकारी | एक टूल नाम को executable + args regex के साथ पंजीकृत करें। पंजीकरण के बिना डिफ़ॉल्ट-अस्वीकार लागू होता है। |
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) अपने async नियम साझा करते हैं,
और shell_true_option एक स्ट्रिंग शेल ({ shell: "/bin/sh" }) या किसी
डायनामिक शेल मान पर भी सक्रिय होता है — केवल लिटरल { shell: true } पर नहीं।
किसी गैर-child_process फ़ंक्शन का promisify, किसी अन्य मॉड्यूल से डीस्ट्रक्चर,
और { shell: false } साफ़ रहते हैं (कोई गलत पॉज़िटिव नहीं)।
// shellguard:ignore-next-line — एक निष्कर्ष को दबाएँ// shellguard:ignore-file — पूरी फ़ाइल को दबाएँ (दुर्लभ; प्रति-पंक्ति को प्राथमिकता दें)