Security-hardened MCP server providing read-only CLI access for AI agents.
See README.md for the full security model and tool reference.
index.mjs Server entry point (security rationale in header comment)
lib/
allowlist.mjs Allowlist matching, flag blocking, JSON array coercion
exec.mjs Sandboxed command execution (execFile, no shell)
tools/
index.mjs Tool registry (barrel export)
<tool>.mjs One file per CLI tool (git, gh, az, npm, pnpm, chezmoi, acli, shell)
test/
<tool>.test.mjs Per-tool security + functionality tests
shell-injection.test.mjs Cross-tool injection attack tests
helpers.mjs Shared test utilities
These are non-negotiable. Every change must preserve them:
- Allowlists, not denylists — commands/subcommands must be explicitly permitted
execFile, no shell — prevents metacharacter injection- Prefix flag matching — defeats CLI flag abbreviation bypass
- No
cwdparameter — except when validated against an enumerable trusted source (seecwdexception criteria inindex.mjsheader)
- Create
tools/<name>.mjs— export a function that takes aMcpServerand registers the tool viaserver.tool() - Define a
SUBCOMMANDSset of allowed subcommands - Define
BLOCKED_FLAGSfor any flags that could mutate state, leak secrets, or enable SSRF - Use
rejectSubcommand()andrejectBlockedFlags()fromlib/allowlist.mjs - Use
exec()fromlib/exec.mjs— neverchild_processdirectly - Re-export from
tools/index.mjs - Add tests in
test/<name>.test.mjscovering allowed commands, blocked subcommands, blocked flags, and injection attempts
- Add the subcommand to the tool's
SUBCOMMANDSset - If the subcommand has sub-subcommands (e.g.,
git stash list), add an allowed-subs set and validate in the handler - Add tests for the new subcommand — both positive and negative cases
Run the full suite before every commit:
pnpm testTests validate the security boundary, not just functionality. Every blocked flag, blocked subcommand, and injection vector has a corresponding test. If you add a new restriction, add a test that proves it blocks the attack. If you relax a restriction, justify it in the commit message.
See .editorconfig. ESM only (.mjs).