high · 8.4CVE-2026-55581Aug 25, 2026

CVE-2026-55581: mcp-shell Secure Mode Allowlist Bypass via /bin/bash -c

Rohit Hatagale
AI Security Researcher, SecureLayer7

The default Docker configuration for mcp-shell lists /bin/bash as an allowed executable, and the validator only checks the first token of a command, so any caller can append '-c <cmd>' to run…

Packagegithub.com/sonirico/mcp-shell
Ecosystemgo
Affected< 0.6.0
Fixed in0.6.0
CVE-2026-55581: mcp-shell Secure Mode Allowlist Bypass via /bin/bash -c

The problem

mcp-shell ships a default security.yaml that includes /bin/bash in allowed_executables. The command validator in security.go extracts only parts[0] (the executable name) and checks it against the allowlist, completely ignoring the rest of the argument list.

Because -c is not a shell metacharacter and blocked_commands defaults to an empty array, passing /bin/bash -c id sails through all checks. The executor then calls exec.CommandContext(ctx, "/bin/bash", "-c", "id"), giving an attacker arbitrary OS command execution inside the container as mcpuser (UID 1000).

No authentication, no configuration change, and no elevated privilege are required.

Proof of concept

A working proof-of-concept for CVE-2026-55581 in github.com/sonirico/mcp-shell, with the exact payload below.

bash
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"poc","version":"0.0.1"}}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"shell_exec","arguments":{"command":"/bin/bash -c id","base64":false}}}' \
| docker run --rm -i sonirico/mcp-shell:latest

# Expected response (truncated):
# {"status":"success","exit_code":0,"stdout":"uid=1000(mcpuser) gid=1000(mcpuser) groups=1000(mcpuser)"}

The root cause is a first-token-only allowlist check (CWE-184, permissive list). security.go sets executable = parts[0], matches /bin/bash in the allowlist, and returns nil without ever inspecting the remaining arguments. The -c flag is not treated as a metacharacter by checkBlockedPatternsAndCommands, so no blocking occurs.

The patch (commit f31377f, v0.6.0) adds an isShellCommandMode function that inspects parts[1:] after the allowlist match. If the executable basename is a known shell (bash, sh, dash, ash, zsh, ksh) and any argument equals -c, the validator returns an error before execution.

The default security.yaml was also updated to remove /bin/bash from allowed_executables entirely, and v0.6.0 logs a startup warning whenever a shell interpreter is found in the allowlist (CWE-1188, insecure default).

The fix

Upgrade to mcp-shell v0.6.0. If you are running a custom security.yaml, remove all shell interpreters (bash, sh, dash, zsh, ksh, etc.) from allowed_executables. If a shell interpreter must be listed, v0.6.0's argument-level check will block -c usage, but the safer posture is to keep interpreters off the list entirely.

Reporter not attributed.

References: [1][2][3][4][5]

Related research