mcpField
S
Stdio Shellguard

Stdio Shellguard

Local
github.comSecurity0Updated 1w ago

About Stdio Shellguard

Stdio Shellguard is a TypeScript security bundle for developers building MCP servers that use stdio. It combines guarded process execution, source auditing, and a reference MCP server exposing the protections as tools.

Key features

  1. 01Allowlisted guarded exec and spawn
  2. 02Sandbox profiles and replay detection
  3. 03AST-based shell-call auditing CLI
  4. 04Scans for 12 unsafe patterns
  5. 05Reference server with eight tools

Use cases

  1. 01Guard shell execution in an MCP server
  2. 02Audit TypeScript or JavaScript for unsafe calls
  3. 03Generate SARIF audit output for CI

MCP support

The reference MCP server exposes eight tools for guarded execution, source auditing, replay checks, sandbox status, and trust tiers.

Configuration

Local server config is the same for every client. Copy it into your client’s MCP config file.

mcp.json · Universal config
{
"mcpServers": {
"stdio-shellguard": {
"command": "npx",
"args": [
"-y",
"mcp-stdio-shellguard"
]
}
}
}
Run commandnpx -y mcp-stdio-shellguard

Local servers need a runtime on your machine (e.g. Node.js 18+). Config file locations differ by client; see your client’s docs.

Packages
npmmcp-stdio-shellguard0.1.2

Tools

8

Saved tool list · updated 3w ago

ParameterTypeRequiredDescription
cwdstringNo
envobjectNo
argsarrayYes
commandstringYes
toolNamestringYes
timeoutMsnumberNo
blockOnReplaybooleanNo

Details

From the project README

Part of the StudioMeyer MCP Stack — Built in Mallorca 🌴 · ⭐ if you use it

mcp-stdio-shellguard

npm version npm downloads License Last commit GitHub stars

Defense-in-depth bundle for MCP stdio servers. Wraps `child_process.exec/spawn`

with allowlist + sandbox + replay-detection, plus an AST audit CLI (mcp-shellguard-audit) that scans MCP server sources for unsanitized shell calls. Closes the Ox-Security MCP stdio-RCE class (200k vulnerable servers, May 2026 disclosure).

  • MCP spec: 2025-06-18
  • SDK: @modelcontextprotocol/sdk ^1.29.0
  • Node: >= 20
  • License: MIT
  • Author: Matthias Meyer (StudioMeyer)

Install

npm install mcp-stdio-shellguard

Or run the audit CLI directly without installing:

npx -y -p mcp-stdio-shellguard mcp-shellguard-audit scan ./src

What it gives you

Three layers, opt-in piecewise:

  1. Library API — drop-in guardExec / guardSpawn you call from your own MCP server. Default-deny allowlist, sandbox profiles, replay window.
  2. Audit CLI — mcp-shellguard-audit scan <path> walks the AST, reports 12 anti-patterns from LOW (no timeout) to CRITICAL (exec(\...${userInput}...`)`).
  3. Reference MCP server — mcp-stdio-shellguard-demo exposes 8 tools so the MCP Inspector / Claude Desktop can drive the bundle directly.

Tools (reference server)

ToolTypePurpose
guard_execdestructiveDefended child_process.exec. Forces args[] vector, allowlist + sandbox + replay. Returns stdout, stderr, exitCode, canonicalHash, isReplay, trustTier.
guard_spawndestructiveDefended child_process.spawn. Returns SHA-256 hashes of stdout/stderr instead of full bodies. Hard-rejects shell:true.
register_allowlistmutatingRegister a tool name with executable + args regex. Without registration the default-deny applies.
audit_sourceread-onlyScan a TS/JS path for shell-injection anti-patterns. Returns AuditFinding[] + summary.
audit_reportread-onlyFormat an audit result as markdown / json / SARIF 2.1.0.
replay_checkread-onlyCompute canonical SHA-256 hash for an invocation and report whether it's already in the replay window.
sandbox_statusread-onlyReport active sandbox profile + concrete limits + cgroup-v2 active flag.
trust_tierread-onlyDerive LOW/MEDIUM/HIGH/CRITICAL tier for a registered tool plus improvement hints.

Sandbox profiles

ProfileTimeoutMax stdoutMax stderrFD budgetcgroup-v2
strict5 s1 MB256 KB32yes (cpu/memory)
standard (default)30 s10 MB1 MB256yes
permissive5 min100 MB10 MB1024no

Caller can tighten via timeoutMs / fdBudget per call. Caller cannot widen beyond the profile.

Trust tiers

TierCondition
LOWtool not registered (default-deny)
MEDIUMregistered but argsPatterns empty (any args allowed)
HIGHargsPatterns set but sandbox or replay tracker inactive
CRITICALargsPatterns + sandbox + replay all active

Lift LOW → CRITICAL by registering the tool + setting argsPatterns + running through guardExec/guardSpawn (which always activate sandbox + replay).

Library quickstart

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

Audit CLI

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

Exit codes:

  • 0 clean (no findings at-or-above floor)
  • 1 findings present
  • 2 parse / IO errors

Custom ignoreGlobs passed to scanPath or the audit_source MCP tool accept at most 128 strings. Each string is limited to 1024 UTF-16 code units and 16 unescaped opening grouping characters ((, {, [). Inputs above these budgets raise a controlled error before matching.

Anti-pattern library (12 rules)

IDSeverityTriggers on
exec_template_literal_with_inputCRITICALchild_process.exec(\ls ${x}`)`
exec_dynamic_stringCRITICALchild_process.exec(cmd)
exec_sync_dynamic_stringCRITICALchild_process.execSync(cmd)
eval_near_child_processCRITICALeval(...)
function_constructor_near_child_processCRITICALnew Function(...)
spawn_dynamic_file_argsHIGHspawn(bin, userArgs)
exec_file_dynamicHIGHexecFile(bin, ...)
shell_true_optionHIGH{ shell: true }
os_system_equivalentHIGHDeno.run / Bun.spawn
spawn_literal_dynamic_argsMEDIUMspawn('git', userArgs)
unbounded_bufferLOWexec without maxBuffer
missing_timeoutLOWexec/spawn without timeout

The scanner resolves renamed child_process bindings before matching, so the dangerous shapes below are caught even when the call goes through an alias rather than a literal 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(...)

Synchronous variants (spawnSync, execFileSync) share their async rules, and shell_true_option also fires on a string shell ({ shell: "/bin/sh" }) or a dynamic shell value — not just the literal { shell: true }. A promisify of a non-child_process function, a destructure off another module, and { shell: false } stay clean (no false positives).

Pragmas

  • // shellguard:ignore-next-line — suppress one finding
  • // shellguard:ignore-file — suppress whole file (rare; prefer per-line)

Why this exists

Ox-Security disclosed (2026-05) that 200k+ MCP stdio servers wrap child_process.exec with template literals carrying user input straight from LLM tool args. LiteLLM v1.83.6 was the canonical example (CVE patched in 1.83.7). This bundle is the defensive-security counterpart: a drop-in guard + scanner that closes the class. Inspired by AWS Linux seccomp + Chromium sandbox tiers.

See also

License

MIT — Copyright (c) 2026 Matthias Meyer (StudioMeyer)

Comments

Markdown supported

New to MCP?

From what the protocol is to installing your first server, these answers cover most first questions.

An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.