Secure MCP server for executing user-defined shell commands via templates.
mcp-secure-exec is a robust Model Context Protocol (MCP) server designed to safely expose shell command functionality to AI clients. It bridges the gap between LLMs and system operations by enforcing strict security policies, input validation, and execution limits. The server supports multiple transport layers, with Streamable HTTP as the recommended default for remote deployments, alongside Stdio for local integrations.
The server supports two primary transport mechanisms for MCP communication:
- Streamable HTTP (Recommended): Suitable for remote connections, supporting stateful sessions, authentication and health checks. Ideal for containerized and cloud deployments.
- Stdio: Ideal for local integrations and piping within existing workflows (e.g., Claude Desktop, local scripts).
- Command Templating Engine: Define safe command structures with named placeholders for arguments.
- Strict Input Validation: Prevents path traversal, shell injection, and dangerous pattern usage.
- Multi-Transport Support: Seamlessly switch between Stdio and Streamable HTTP transports.
- Rate Limiting & Concurrency Control: Protects resources with configurable request limits and concurrent execution caps.
- Circuit Breaker Protection: Automatically halts execution during repeated failures to prevent cascading errors.
- Comprehensive Audit Logging: Tracks command invocations and security events with redacted sensitive data.
- Graceful Shutdown: Handles SIGINT/SIGTERM for clean termination.
services:
mcp-secure-exec:
image: timasoft/mcp-secure-exec:0.1.0
container_name: mcp-secure-exec
restart: unless-stopped
environment:
MCP_EXEC_COMMANDS: 'echo|"echo {message}"'
MCP_EXEC_TRANSPORT: streamable-http
MCP_EXEC_BIND: 0.0.0.0:3344
MCP_EXEC_AUTH_TOKEN: 'very_secret_token'
MCP_EXEC_LOG_LEVEL: info
security_opt:
- no-new-privileges: true
read_only: true
tmpfs:
- /tmp
ports:
- "127.0.0.1:3344:3344"
⚠️ YAML Quoting: Always quote environment variable values containing special characters like|or{}to prevent YAML parsing errors.
Important notes about configuration:
- Security Context: Always run with
no-new-privilegesand read-only filesystem where possible. - Command Definition: Commands must be defined via environment variables or CLI arguments; no commands are allowed by default.
- Network Binding: When using HTTP transport, bind to
127.0.0.1unless external access is explicitly required. - Authentication: Enable
MCP_EXEC_AUTH_TOKENfor HTTP transport to require Bearer token authentication. - Sensitive Data: Use
MCP_EXEC_SENSITIVE_KEYSto ensure secrets are not logged in plain text.
Make sure to:
- Define at least one command template using
MCP_EXEC_COMMANDS. - Set
MCP_EXEC_TRANSPORT=streamable-httpfor remote access (default isstdio). - Verify binary availability within the container environment.
After adding the service, run:
docker-compose up -d
docker-compose logs -f mcp-secure-execVerify HTTP server is running:
curl http://127.0.0.1:3344/healthIf you're using Nix or NixOS, you can build and run the application directly:
Streamable HTTP mode (recommended for remote/server use):
nix run github:timasoft/mcp-secure-exec -- \
--cmd 'status|"systemctl status {service}"' \
--transport streamable-http \
--bind 127.0.0.1:3344 \
--auth-token your_secret_tokenStdio mode (for local MCP clients like Claude Desktop):
nix run github:timasoft/mcp-secure-exec -- \
--cmd 'echo|"echo {message}"' \
--cmd 'date|"date"' \
--transport stdioWith path restrictions and security hardening:
nix run github:timasoft/mcp-secure-exec -- \
--cmd 'cat|"cat {path}"' \
--base-path /home/user \
--cmd-timeout 10 \
--log-level debug-
Install Rust toolchain:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-
Install the project:
cargo install mcp-secure-exec
-
Run the application:
Streamable HTTP mode (recommended):
mcp-secure-exec \ --cmd 'uptime|"uptime"' \ --transport streamable-http \ --bind 127.0.0.1:3344 \ --auth-token secure_tokenStdio mode (for local MCP clients):
mcp-secure-exec \ --cmd 'echo|"echo {message}"' \ --cmd 'ls|"ls -la {path}"' \ --base-path /home/user \ --transport stdio
Environment variable configuration (recommended):
export MCP_EXEC_COMMANDS='echo|"echo {msg}"' export MCP_EXEC_TRANSPORT=streamable-http export MCP_EXEC_AUTH_TOKEN='very_secret_token' export MCP_EXEC_LOG_LEVEL=info mcp-secure-exec
Dry-run validation (test configuration without starting server):
mcp-secure-exec --cmd 'test|"echo {arg}"' --dry-run
All configuration options can be set via environment variables. CLI arguments take precedence over environment variables. Multiple commands can be specified using semicolon delimiter in MCP_EXEC_COMMANDS.
| Variable | Description | Default |
|---|---|---|
MCP_EXEC_COMMANDS |
Command definitions in format name|"template" (semicolon-delimited for multiple) |
Required |
MCP_EXEC_DANGEROUS |
Comma-separated list of restricted binaries (case-insensitive matching) | rm,dd,mkfs,chmod,chown,sudo,su,curl,wget,sh,bash,dash,zsh,python,python3,perl,ruby,node,lua,php,awk,sed,gawk,git,ssh,scp,rsync,tar,zip,unzip,gzip,bzip2,find,xargs,env,nice,timeout,strace,ltrace,ncat,netcat,nc,telnet,ftp,sftp,rbash,rsh |
MCP_EXEC_ALLOW_DANGEROUS |
Allow execution of restricted binaries (true/false) |
false |
MCP_EXEC_BASE_PATH |
Restrict path-like arguments to this directory | None |
MCP_EXEC_NO_VALIDATE_PATHS |
Disable path traversal protection (true/false) |
false |
MCP_EXEC_ALLOW_MISSING_BINARIES |
Allow commands whose binaries are not in PATH at startup (true/false) |
false |
⚠️ --allow-missing-binariesbehavior: This flag defers binary existence checks from startup to runtime. While useful for testing or dynamic environments, it increases runtime risk: a command may fail unexpectedly if the binary is missing or has been tampered with.
| Variable | Description | Default |
|---|---|---|
MCP_EXEC_SENSITIVE_KEYS |
Comma-separated list of argument names to mask in logs (case-insensitive) | password,token,secret,key,auth,credential |
MCP_EXEC_AUTH_TOKEN |
Optional Bearer token for HTTP authentication | None |
MCP_EXEC_LOG_LEVEL |
Logging verbosity level | info |
MCP_EXEC_DRY_RUN |
Validate configuration and exit (true/false) |
false |
| Variable | Description | Default |
|---|---|---|
MCP_EXEC_TRANSPORT |
Communication protocol (stdio or streamable-http) |
stdio |
MCP_EXEC_BIND |
Network address for HTTP server | 127.0.0.1:3344 |
MCP_EXEC_STATEFUL |
Enable stateful sessions for HTTP transport (true/false) |
false |
| Variable | Description | Default |
|---|---|---|
MCP_EXEC_CMD_TIMEOUT_SECS |
Maximum execution time per command in seconds | 30 |
MCP_EXEC_RATE_LIMIT_RPS |
Maximum requests per second | 10 |
MCP_EXEC_RATE_LIMIT_BURST |
Burst size for rate limiting | 20 |
MCP_EXEC_MAX_CONCURRENT |
Maximum concurrent command executions | 50 |
MCP_EXEC_CIRCUIT_THRESHOLD |
Circuit breaker failure threshold | 10 |
MCP_EXEC_CIRCUIT_TIMEOUT_SECS |
Circuit breaker timeout in seconds | 60 |
Default Security Posture:
MCP_EXEC_DANGEROUSincludes common system utilities likerm,sudo, and shells to prevent accidental system damage. Blacklist matching is case-insensitive —RM,Rm, andrmare all blocked.
Usage: mcp-secure-exec [OPTIONS]
Options:
-c, --cmd <NAME|"TEMPLATE">
Define a command tool (can be repeated; use ';' separator) [env: MCP_EXEC_COMMANDS=]
-B, --blacklist <DANGEROUS>
Comma-separated list of blacklisted binaries. [env: MCP_EXEC_DANGEROUS=] [default: rm,dd,mkfs,chmod,chown,sudo,su,curl,wget,sh,bash,dash,zsh,python,python3,perl,ruby,node,lua,php,awk,sed,gawk,git,ssh,scp,rsync,tar,zip,unzip,gzip,bzip2,find,xargs,env,nice,timeout,strace,ltrace,ncat,netcat,nc,telnet,ftp,sftp,rbash,rsh]
-D, --allow-dangerous
Allow execution of blacklisted binaries. USE WITH CAUTION. [env: MCP_EXEC_ALLOW_DANGEROUS=]
--no-validate-paths
Disable path traversal protection for path-like placeholders. [env: MCP_EXEC_NO_VALIDATE_PATHS=]
--allow-missing-binaries
Allow commands whose binaries are not in PATH. [env: MCP_EXEC_ALLOW_MISSING_BINARIES=]
--base-path <BASE_PATH>
Restrict path-like arguments to be within this base directory. [env: MCP_EXEC_BASE_PATH=]
--sensitive-keys <SENSITIVE_KEYS>
Comma-separated list of argument names to mask in logs. [env: MCP_EXEC_SENSITIVE_KEYS=] [default: password,token,secret,key,auth,credential]
--cmd-timeout <CMD_TIMEOUT_SECS>
Maximum execution time for commands in seconds. [env: MCP_EXEC_CMD_TIMEOUT_SECS=] [default: 30]
--log-level <LOG_LEVEL>
[env: MCP_EXEC_LOG_LEVEL=] [default: info] [possible values: info, debug, trace, warn, error]
-d, --dry-run
[env: MCP_EXEC_DRY_RUN=]
-t, --transport <TRANSPORT>
[env: MCP_EXEC_TRANSPORT=] [default: stdio] [possible values: stdio, streamable-http]
-b, --bind <BIND>
[env: MCP_EXEC_BIND=] [default: 127.0.0.1:3344]
--auth-token <AUTH_TOKEN>
Optional Bearer token for authentication. [env: MCP_EXEC_AUTH_TOKEN=]
-s, --stateful
Enable stateful sessions (default: false). [env: MCP_EXEC_STATEFUL=]
--rate-limit-rps <RATE_LIMIT_RPS>
Maximum requests per second (Rate Limiting). [env: MCP_EXEC_RATE_LIMIT_RPS=] [default: 10]
--rate-limit-burst <RATE_LIMIT_BURST>
Burst size for rate limiting. [env: MCP_EXEC_RATE_LIMIT_BURST=] [default: 20]
--max-concurrent <MAX_CONCURRENT>
Maximum concurrent command executions. [env: MCP_EXEC_MAX_CONCURRENT=] [default: 50]
--circuit-threshold <CIRCUIT_THRESHOLD>
Circuit breaker failure threshold. [env: MCP_EXEC_CIRCUIT_THRESHOLD=] [default: 10]
--circuit-timeout <CIRCUIT_TIMEOUT_SECS>
Circuit breaker timeout in seconds. [env: MCP_EXEC_CIRCUIT_TIMEOUT_SECS=] [default: 60]
-h, --help
Print help
-V, --version
Print versionPlaceholder names in command templates trigger specific validation behaviors:
| Placeholder Pattern | Effect | Example |
|---|---|---|
path, file, dir, filepath |
Enables path traversal protection; requires --base-path for relative paths |
{path}, {file} |
*_flag, *_opt |
Allows safe single-dash flags like -h; dangerous flags (e.g., -v, -x, -e) are blocked regardless |
{verbose_flag} → -h OK, -v rejected |
| Any other name | Blocks shell metacharacters and command-like patterns | {message} → hello;rm rejected |
💡 Note: Even with
*_flagsuffix, flags listed in the dangerous patterns blacklist (like-v,-x,-e,--exec, etc.) are always blocked for security. Use only safe, non-dangerous flags with flag placeholders.
Remote Server with Authentication
mcp-secure-exec \
--cmd 'status|"systemctl status {service}"' \
--transport streamable-http \
--bind 127.0.0.1:3344 \
--auth-token secure_tokenEnvironment Variable HTTP Configuration
export MCP_EXEC_TRANSPORT=streamable-http
export MCP_EXEC_AUTH_TOKEN='very_secret_token'
export MCP_EXEC_COMMANDS='status|"systemctl status {service}"'
mcp-secure-execHealth Check Verification
curl http://127.0.0.1:3344/healthMultiple Tools with HTTP
mcp-secure-exec \
--cmd 'date|"date"' \
--cmd 'uptime|"uptime"' \
--cmd 'df|"df -h {mount}"' \
--transport streamable-http \
--bind 0.0.0.0:3344 \
--auth-token very_secret_tokenBasic Echo Tool
mcp-secure-exec --cmd 'echo|"echo {message}"' --log-level debug --transport stdioFile Listing with Path Validation
mcp-secure-exec --cmd 'ls|"ls -la {path}"' --base-path /home/user --transport stdioMultiple Tools Configuration
mcp-secure-exec --cmd 'date|"date"' --cmd 'uptime|"uptime"' --transport stdioOR
mcp-secure-exec --cmd 'date|"date";uptime|"uptime"' --transport stdiomcp-secure-exec --cmd 'test|"echo {arg}"' --dry-runMCP_EXEC_DRY_RUN=true MCP_EXEC_COMMANDS='test|"echo {arg}"' mcp-secure-exec- Use
streamable-httptransport with the server URL:http://host:3344 - Include
Authorization: Bearer <token>header ifMCP_EXEC_AUTH_TOKENis set - Health endpoint:
GET /healthreturnsOK
- Use
stdiotransport for local subprocess execution inclaude_desktop_config.json - Example configuration:
{
"mcpServers": {
"mcp-secure-exec": {
"command": "mcp-secure-exec",
"args": [
"--cmd", "echo|\"echo {message}\"",
"--cmd", "ls|\"ls -la {path}\"",
"--base-path", "/home/user",
"--transport", "stdio"
]
}
}
}transport- Ensure the client supports the selected transport protocol.commands- Verify tool names match the--cmdname definition exactly.
Install MCP Clients using:
npm install -g @modelcontextprotocol/inspectormcp-secure-exec follows a layered security architecture designed to isolate command execution from the MCP protocol handling.
- Implements Streamable HTTP Server spec for MCP.
- Supports optional Bearer token authentication via
Authorizationheader. - Includes
/healthendpoint for load balancer and orchestration checks. - Graceful shutdown via SIGINT/SIGTERM handling with
CancellationToken.
- Direct pipe communication between client and server.
- Minimal overhead, suitable for local agents.
- Inherits parent process environment and permissions.
- Terminates when stdin is closed.
- Security Layer: Validates arguments against injection patterns and path traversal attempts.
- Recursive URL-decoding protection (prevents
%252e%252e→..attacks) - Unicode normalization (NFC/NFD) to block homoglyph bypasses
- Contextual shell metacharacter filtering
- Recursive URL-decoding protection (prevents
- Execution Engine: Spawns processes with timeout enforcement and output capture.
- Rate Limiter: Token bucket algorithm to prevent abuse.
- Circuit Breaker: Opens circuit after threshold failures to protect stability.
- Audit System: Logs all invocations with sensitive data masking.
The server recursively decodes URL-encoded input (up to 5 iterations) to detect and block encoded path traversal attempts like %252e%252e%252f (triple-encoded ../).
Input is normalized to both NFC and NFD forms to prevent bypasses using Unicode homoglyphs or combining characters.
- Path placeholders (
{path},{file}, etc.) allow quoted metacharacters for legitimate use cases. - Non-path placeholders strictly block shell metacharacters outside quotes.
- Flag placeholders (
*_flag) permit safe single-dash options while still blocking dangerous patterns like-v,-x,--exec, etc.
mcp-secure-exec --cmd 'test|"echo test"' --log-level traceMCP_EXEC_LOG_LEVEL=trace mcp-secure-exec --cmd 'test|"echo test"'- Check PATH:
which <binary>orecho $PATH - Allow Missing: Use
--allow-missing-binariesfor testing (defers check to runtime)
- Verify Binding: Ensure
--bindaddress is accessible from the checker. - Check Logs: Review stdout/stderr for startup errors like
FATAL: Binary not found. - Firewall: Confirm port 3344 (or custom bind) is not blocked by host firewall.
- Authentication: If
MCP_EXEC_AUTH_TOKENis set, includeAuthorization: Bearer <token>in requests.
mcp-secure-exec is designed with security as a primary concern. However, please note:
- Never use
--allow-dangerousunless you fully understand the risks. - Always use
--base-pathwhen exposing file operations. - Enable
MCP_EXEC_AUTH_TOKENfor HTTP transport. - Blacklist matching is case-insensitive:
RM,Sudo, andbAsHare blocked just like their lowercase forms. --allow-missing-binariesdefers security checks: A command may pass startup validation but fail at runtime if the binary is missing or has been replaced.- Audit logs redact sensitive data: Arguments matching
MCP_EXEC_SENSITIVE_KEYSare masked as[REDACTED]. - Dangerous flags are always blocked: Even with
*_flagplaceholders, flags like-v,-x,-e,--exec, etc. are rejected regardless of context.