# shell-mcp A super-secure MCP (Model Context Protocol) server for running shell commands safely with a local LLM client. ## Security Model Commands pass through **8 independent security layers** — every one must pass: | Layer | What it does | |-------|-------------| | 1 | **Zod schema validation** — strict type checks before any processing | | 2 | **No-shell spawn** — `execFile` with `shell: false`, no `/bin/sh -c "..."` ever | | 3 | **Hardcoded blocklist** — `rm`, `sudo`, `dd`, `curl`, `brew`, `docker`, etc. permanently blocked | | 4 | **Argument inspection** — rejects metacharacters, null bytes, suspicious patterns, per-command dangerous flags | | 5 | **Directory allowlist** — every path must be inside `--allow-dir` | | 6 | **Symlink resolution** — symlinks resolved before path check (no escape via symlink) | | 7 | **Resource limits** — timeout, output size cap, concurrency limit | | 8 | **Sanitized environment** — strips `AWS_*`, `*_TOKEN`, `*_SECRET`, all cloud provider secrets | ## Quick Start ```bash # Clone and build git clone https://github.com/your-username/shell-mcp.git cd shell-mcp npm install npm run build # Run (must specify at least one --allow-dir) node dist/index.js --allow-dir ~/projects --allow-dir ~/data ``` ## LLM Client Configuration ```json { "shell-mcp": { "command": "node", "args": [ "/path/to/shell-mcp/dist/index.js", "--allow-dir", "/home/user/projects", "--allow-dir", "/home/user/data", "--allow-cmd", "ls", "--allow-cmd", "cat", "--allow-cmd", "grep", "--allow-cmd", "find", "--allow-cmd", "python3", "--timeout", "30", "--log-file", "/home/user/.shell-mcp/audit.log" ] } } ``` ## Flags | Flag | Description | Default | |------|-------------|---------| | `--allow-dir ` | Permitted directory (required, repeatable) | *none — server won't start* | | `--allow-cmd ` | Whitelist specific commands (repeatable) | *all non-blocked* | | `--block-cmd ` | Extra commands to block | — | | `--timeout ` | Max execution time (1–300) | 30 | | `--max-output ` | Max output size | 1048576 (1MB) | | `--max-concurrent ` | Max parallel commands (1–10) | 3 | | `--log-file ` | Audit log file path | stderr only | | `--enable-write` | Enable `write_file` tool | off | | `--allow-overwrite` | Permit overwriting files | off | | `--read-only-mode` | Disables all writes (overrides `--enable-write`) | off | | `--allow-network-cmds` | Unblock curl, wget, nc | off | | `--allow-env-passthrough` | Forward full env to child processes | off (sanitized) | ## Tools Exposed to the LLM ### `run_command` Run a command with explicit arguments array. **No shell** — pipes and redirects won't work. ```json { "command": "grep", "args": ["-r", "TODO", "/home/user/projects/myapp"], "cwd": "/home/user/projects/myapp", "timeout": 10 } ``` ### `read_file` Read a file's contents. Supports line ranges and binary (base64) output. ```json { "path": "/home/user/projects/myapp/src/main.py", "startLine": 1, "endLine": 50 } ``` ### `list_directory` List a directory with tree view, sizes, and permissions. ```json { "path": "/home/user/projects/myapp", "recursive": true } ``` ### `write_file` *(requires `--enable-write`)* Write a file. Won't overwrite without `--allow-overwrite`. ```json { "path": "/home/user/projects/myapp/notes.txt", "content": "Hello world" } ``` ### `get_server_info` Returns current server config and restrictions. Useful for the LLM to understand its sandbox before acting. ## Permanently Blocked Commands These **cannot** be enabled by any flag: ``` rm, rmdir, shred, dd, mkfs, fdisk, diskutil, sudo, su, doas, osascript, defaults, open, launchctl, crontab, curl, wget, nc, ssh, scp, rsync, brew, npm, yarn, pip, gem, cargo, docker, gcc, clang, kill, killall, shutdown, reboot, gpg, security (macOS keychain), ...and more ``` ## Audit Log Every command attempt (allowed or denied) is logged in JSON: ```json {"timestamp":"2024-01-01T12:00:00.000Z","sessionId":"A3F9B2C1","event":"COMMAND_DENY","command":"rm","args":["-rf","/"],"denyLayer":3,"denyReason":"Command \"rm\" is on the hardcoded blocklist"} {"timestamp":"2024-01-01T12:00:01.000Z","sessionId":"A3F9B2C1","event":"COMMAND_ALLOW","command":"ls","args":["-la"],"cwd":"/home/user/projects","exitCode":0,"durationMs":12,"outputBytes":1024} ``` ## Inspect with MCP Inspector ```bash npx @modelcontextprotocol/inspector node dist/index.js --allow-dir /tmp/test ```