mcp-shell acts on behalf of an untrusted MCP client (typically an LLM that
may be prompt-injected). Secure mode (the default, security.enabled: true)
never lets the client supply a shell string: it registers only typed tools,
each of which builds its own fixed argv server-side from a small set of
declared parameters. There is no shell_exec in this mode.
What secure mode guarantees:
- No shell interpretation. Every tool invokes a specific program with a server-constructed argument list. There is no command string to parse, inject into, or escape from.
- Path confinement. Every path parameter (file tools and git tools alike)
is resolved against
working_directorywith symlinks followed; a path that resolves outside it is rejected. - Git hardening. Paths are passed to git after
--, refs after--end-of-options, and a ref starting with-is rejected outright. Git runs withGIT_CONFIG_NOSYSTEM=1,GIT_CONFIG_GLOBAL=/dev/null,-c core.fsmonitor=false -c core.pager=cat -c core.hooksPath=/dev/null, and--no-ext-diff --no-textconvonlog/diff/show/blame, so no operator- or attacker-controlled hook, pager, or external diff driver runs. - Minimal environment. Child processes (git and
run_scriptalike) get onlyPATH,HOMEandLANG- never the server's own environment or any.envloaded at startup, so a tool cannot recover secrets held by the server or exported by the MCP client. - Writes are gated.
write_file,edit_file,mkdir,move,delete, and the git write tools (git_add,git_commit,git_switch,git_restore,git_stash) are only registered when the operator setswrites_enabled: true. They are absent from the tool list otherwise, not merely refused at call time. - Scripts are gated and operator-defined.
run_scriptis only registered when the operator'sscriptsmap is non-empty. The client picks a script by name; it cannot alter or extend the argv the operator configured for it. - Output is capped. Tool output is truncated to
max_output_size.
What secure mode does not guarantee:
- It is not an OS sandbox. It does not confine filesystem, network or resource usage of a command once it runs. Real containment comes from the deployment: run the provided Docker image (non-root), ideally with a read-only rootfs, dropped capabilities and no network, or an equivalent OS-level sandbox.
run_scriptexecutes the operator's own configured argv as-is. mcp-shell does not vet that argv; whatever the operator put there is what runs.MCP_SHELL_ALLOW_UNSAFE=1disables secure mode entirely by explicit opt-in and replaces the typed tools with a singleshell_exectool that runsbash -con the client's command string with no validation at all.- git can still mutate refs within its own repository. The git tools do
not execute programs, write outside the repo, or reach the network, but with
writes_enabled: true,git_switch(withcreate) andgit_stashcan still create branches or drop stashes in the repository git runs in. That is bounded to the repo (no arbitrary-path write, no execution) and is a known, lower-severity gap. If even repo-local mutation is unacceptable, run git under a read-only OS sandbox or leavewrites_enabled: false.
In scope is the default mode only: typed tools, path confinement, git hardening, and config loading. Please report:
- A path parameter that escapes
working_directory(a symlink or traversal that is not rejected). - A git tool that reaches the network, executes an external program (hook,
pager, diff/textconv driver), or accepts a ref/path that is not confined to
the repository despite the
--/--end-of-optionsand leading--rejection described above. - A way to reach
write_file,edit_file,mkdir,move,delete, a git write tool, orrun_scriptwithout the operator having setwrites_enabled: trueor a non-emptyscriptsmap, respectively. - A config file that is accepted despite setting a key that was removed in 1.0.0 (see the migration notes in README.md).
Reports about shell_exec under MCP_SHELL_ALLOW_UNSAFE=1 are out of scope
and will be closed: that mode is arbitrary command execution by design.
Out of scope (known limitations, not vulnerabilities):
- Anything requiring
MCP_SHELL_ALLOW_UNSAFE=1. - Resource exhaustion, information disclosure, or filesystem reads by
read_file/grep/glob/list_dirwithin the working directory contract (reading a readable file is what those tools are for). Confinement toworking_directoryis the guarantee; containment of what's inside it is the sandbox's job. run_scriptrunning whatever argv the operator configured for it. The operator chose that argv; mcp-shell does not vet it.- The repo-local ref mutation described above (
git_switch --create,git_stash) whenwrites_enabled: true.
Use GitHub private vulnerability reporting on this repository. Include a runnable proof of concept against the default configuration or a minimal config diff. Reports matching the in-scope list are triaged and credited; out-of-scope reports are closed with a pointer to this document.