Skip to content

Security: sonirico/mcp-shell

SECURITY.md

Security Policy

Threat model

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_directory with 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 with GIT_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-textconv on log/diff/show/blame, so no operator- or attacker-controlled hook, pager, or external diff driver runs.
  • Minimal environment. Child processes (git and run_script alike) get only PATH, HOME and LANG - never the server's own environment or any .env loaded 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 sets writes_enabled: true. They are absent from the tool list otherwise, not merely refused at call time.
  • Scripts are gated and operator-defined. run_script is only registered when the operator's scripts map 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_script executes 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=1 disables secure mode entirely by explicit opt-in and replaces the typed tools with a single shell_exec tool that runs bash -c on 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 (with create) and git_stash can 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 leave writes_enabled: false.

Scope for vulnerability reports

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-options and leading-- rejection described above.
  • A way to reach write_file, edit_file, mkdir, move, delete, a git write tool, or run_script without the operator having set writes_enabled: true or a non-empty scripts map, 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_dir within the working directory contract (reading a readable file is what those tools are for). Confinement to working_directory is the guarantee; containment of what's inside it is the sandbox's job.
  • run_script running 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) when writes_enabled: true.

Reporting

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.

Learn more about advisories related to sonirico/mcp-shell in the GitHub Advisory Database