An MCP server that gives an LLM one tool, execute_code: run a short
bash or python program in an isolated, network-disabled Docker container and
get back its stdout, stderr, and exit code. It's a standalone Go service —
a sibling project to Miranda, not a package inside it — meant
to be wired into Miranda (or any other MCP client) as one more tool source.
MCP client (Miranda, Claude, ...) <--Streamable HTTP--> code-execution-sandbox <--docker run--> ephemeral sandbox container
The server itself runs host-native — a single static Go binary, no
Docker for the process itself — and talks to the local Docker daemon to
spawn a fresh, hardened container per call. See CLAUDE.md for the full
security model and why this isn't "just run it in Docker too."
Requires Go 1.25+ (the module pins go 1.25.0; with GOTOOLCHAIN=auto —
the default — go build fetches a matching toolchain automatically) and a
working Docker install (docker CLI + daemon, docker compose).
go build -o code-execution-sandbox ./cmd/code-execution-sandbox
# or
make buildCross-compiling for another host (e.g. the deploy target) is just:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o code-execution-sandbox-linux-amd64 ./cmd/code-execution-sandboxEvery execute_code call runs against a prebuilt image
(docker/sandbox.Dockerfile), never a base image pulled at request time:
docker compose build sandbox
# or
make docker-buildcp config/config.yaml.dist config/config.yaml # optional — every field has a default
cp .env.example .env
# fill in SANDBOX_MCP_TOKEN in .env
make docker-build # build the sandbox runtime image, once
make run # build + run the serverThe server listens on :8788 by default: GET /healthz (unauthenticated;
also checks the local Docker daemon is reachable) and POST /mcp (the MCP
endpoint, requires Authorization: Bearer <SANDBOX_MCP_TOKEN>).
make test # go test ./... -race — unit tests everywhere;
# internal/sandbox's and test/integration's
# Docker-backed tests skip automatically if the
# sandbox image hasn't been built yet
make docker-test # docker-build + test — the full suite, nothing skipped
make lint # golangci-lint run ./...
make fmt # gofmt + goimports
make check # fmt + lint + test — run this before committingmake lint/make check need golangci-lint and goimports on PATH —
make tools installs both.
./scripts/deploy.shCross-compiles for linux/amd64, ships the binary plus the Docker build
context over SSH, rebuilds the sandbox runtime image on the server, and
restarts the systemd --user service that runs it. See
.claude/skills/deploy/SKILL.md for the full breakdown.
Copy config/config.yaml.dist to config/config.yaml and edit it — every
field has a built-in default (see internal/config.Default()), so you only
need to override what differs:
http_addr: ":8788"
auth_token_env: "SANDBOX_MCP_TOKEN"
logging:
level: "info"
sandbox:
image: "code-execution-sandbox-runtime:latest"
max_concurrent: 4
default_timeout_seconds: 10
max_timeout_seconds: 60
max_code_bytes: 65536
max_output_bytes: 1048576
memory_limit: "1g"
cpu_limit: "1"
pids_limit: 128The auth token itself is never put in config.yaml — auth_token_env
names an environment variable to read it from at startup. For local
development, copy .env.example to .env and fill it in (loaded via
internal/envfile; a variable already set in the real environment always
wins). The server refuses to start if that variable is unset or empty.
Logs go to stdout as log/slog text output (same as Miranda), which lands
in the journal under systemd --user — see .claude/skills/deploy. Set
logging.level: "debug" to log every incoming execute_code call
(language, timeout, and the full submitted code, not just its size) plus
its result (exit code, duration, timed-out flag). This is deliberately more
verbose than Miranda's usual debug-log convention of summarized fields —
seeing exactly what a client ran is the point — so leave it at "info"
unless you're actively diagnosing something.
Add an entry to Miranda's config/mcp.yaml:
mcp:
servers:
- name: code_exec
url: "http://192.168.1.50:8788/mcp"
token_env: "SANDBOX_MCP_TOKEN"
enabled: trueand set SANDBOX_MCP_TOKEN in Miranda's own .env to the same value
configured here. Miranda will then expose the tool as code_exec_execute_code
(see Miranda's internal/mcp.Manager tool-name prefixing).
See CLAUDE.md for the full write-up. Summary: every call runs in a fresh,
--rm'd container with --network=none, a read-only root filesystem (only
a small noexec tmpfs at /tmp), --cap-drop=ALL, --security-opt no-new-privileges, a non-root user, and hard CPU/memory/pids/time limits.
Code is piped over stdin, never placed in argv or a shell string, so there's
no injection surface. Nothing persists between calls, and no host filesystem
or Docker socket is ever exposed to the sandboxed process.
cmd/code-execution-sandbox/ entrypoint: config, wiring, HTTP listen, graceful shutdown
internal/config/ Default()+YAML config
internal/envfile/ .env loader
internal/sandbox/ Executor interface + Docker-backed Runner (the security-critical part)
internal/mcpserver/ the execute_code MCP tool
internal/httpserver/ bearer-token auth + /healthz
docker/sandbox.Dockerfile hardened runtime image (python3 + bash, curl/ffmpeg/faster-whisper, non-root)
docker-compose.yml builds the runtime image (never `up`, see CLAUDE.md)
test/integration/ real HTTP + real MCP wire protocol, end to end