Dev Tools·
advanced
·14 min read·Jul 9, 2026

Designing Secure Execution Sandboxes for Bash & Shell Tool Calls

Learn how to build secure sandboxes using Docker containers and system boundaries to run terminal commands safely via MCP.

sandboxsecuritybashDockerexecution

Secure Execution Sandboxes for Bash & Shell Tool Calls

Giving an LLM agent access to run shell commands (/bin/bash or cmd.exe) directly on your host machine is a critical security vulnerability. An agent could accidentally rm -rf important directories, leak environment variables, or download malicious software via arbitrary curl commands.

Prerequisites

  1. Docker Desktop or the Docker Engine installed and running on your host machine.
  2. Node.js installed to wrap the Docker execution in an MCP server.

Architecture of a Sandboxed MCP Execution Server

To run bash scripts safely, execute the commands within an isolated Docker container configured with CPU constraints, strict memory limits, and no host network bridge:

code
[MCP Client] --> [Bash MCP Server] --> [Spawns Ephemeral Docker Container]
                                              |
                                              +---> CPU Limit: 0.5 Cores
                                              +---> Memory Limit: 512MB
                                              +---> Read-Only Host Mounts
                                              +---> No Internet Bridge

Node.js Docker-based Shell Execution Script

Here is an example TypeScript implementation of a tool that accepts an agent's shell command and safely executes it inside an Alpine Linux sandbox:

typescript
import { exec } from 'child_process';
import util from 'util';
const execPromise = util.promisify(exec);

async function runCommandInSandbox(userCommand: string): Promise<string> {
  // Deep sanitization is still necessary to prevent command injection into the Docker CLI itself
  const sanitized = userCommand.replace(/'/g, "'\\''");
  
  const dockerCmd = `docker run --rm \
    --network none \
    --memory 512m \
    --cpus 0.5 \
    alpine:latest sh -c '${sanitized}'`;

  try {
    const { stdout, stderr } = await execPromise(dockerCmd, { timeout: 10000 });
    return stdout || stderr;
  } catch (err: any) {
    return `Execution Error: ${err.message}`;
  }
}

Best Practices

  • Drop Network Access: By using --network none, you guarantee the agent cannot curl or wget external scripts, blocking potential data exfiltration or malware downloads.
  • Ephemeral Containers: The --rm flag ensures the container is immediately destroyed after the command finishes. State should not persist between tool calls.
  • Hard Timeouts: Enforce a strict timeout (e.g., 10 seconds) so the agent cannot write an infinite while true loop that hogs host resources.

Troubleshooting

  • docker: command not found: Ensure the Docker daemon is running and the docker CLI is in the PATH of the environment executing the MCP server.
  • Files Not Found: Since the execution happens in an isolated Alpine container, it has no access to your host filesystem. If you want the agent to read local files, you must explicitly bind-mount them using -v /path/to/host:/app:ro (read-only!).

Related Guides