This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A code execution sandbox service that runs arbitrary code inside Linux namespace jails (nsjail). Supports Node.js, TypeScript, Ruby, Go, Python, Rust, and Bash runtimes. Exposes an HTTP API to receive code, execute it in an isolated environment, and return the output.
Tool versions are managed by mise. Run mise install to install Go, golangci-lint, and lefthook.
- Go: 1.26.0 (mise.toml / go.mod)
- golangci-lint: 2.10.1 (installed via mise aqua backend)
- lefthook: 2.1.2 (installed via mise aqua backend) — pre-commit hook runs
go fmt ./...,golangci-lint run, andgitleaks git --pre-commit --staged; pre-push hook runsgitleaks git
# Build the server binary
go build -o sandbox .
# Run locally (requires nsjail + Node.js + Ruby + Go at hardcoded paths; use Docker instead)
docker compose up --build
# Restart with rebuild (for E2E testing after code changes)
docker compose down && docker compose up --build -d
# Lint
golangci-lint run
# Run unit tests
go test ./...
# Run E2E tests (requires running server via docker compose)
go test -tags e2e ./e2e/...The container must run in privileged mode (required for nsjail to create Linux namespaces).
--port(default8080, overridden byPORTenv var) — port to listen on--run-timeout(default30) — sandbox run timeout in seconds--compile-timeout(default30) — sandbox compile timeout in seconds--output-limit(default1048576/ 1 MiB) — maximum combined output bytes--max-files(default10) — maximum number of files per request--max-file-size(default262144/ 256 KiB) — maximum file size in bytes per file--max-stdin-size(default1048576/ 1 MiB) — maximum stdin size in bytes per request (post-decode whenbase64_encodedis true; wire bytes otherwise)--max-body-size(default5242880/ 5 MiB) — maximum request body size in bytes--max-concurrency(default10) — maximum number of concurrent sandbox executions--max-queue-size(default50) — maximum number of requests waiting in the execution queue--queue-timeout(default30) — maximum time in seconds a request waits in the execution queue--metrics(defaultfalse) — enable the/metricsendpoint
POST /v1/run → main.go → cmd/serve.go (Cobra CLI, Echo v5 router)
→ internal/handler/handler.go (validate runtime, reject restricted files, optional base64 decode of files/stdin, validate filenames, write to tmpdir)
→ internal/sandbox/sandbox.go (invoke nsjail with the selected runtime; stdin piped to the run step only)
→ Response: {compile, run} where each contains {stdout, stderr, output, exit_code, status, signal, duration_ms} (stdout/stderr/output are base64-encoded)
- cmd/ — CLI entrypoint using Cobra.
- cmd/gocacheprog/ — Read-only Go module cache helper used during compilation.
- internal/handler/ — Request parsing and response formatting.
- internal/sandbox/ — Core sandbox execution logic;
configs/nsjail.cfgholds the static nsjail protobuf config;configs/seccomp.kafelholds the Seccomp-BPF syscall filtering policy. - internal/middleware/ — Custom Echo middleware. Concurrency limiter with queue management.
- e2e/ — YAML-driven E2E test suite. Test cases live under
e2e/tests/runtime/,e2e/tests/security/, ande2e/tests/api/. Seee2e/CLAUDE.mdfor testing guidelines.
Four-stage Dockerfile (mise → base → builder → final). The base stage uses ghcr.io/codize-dev/nsjail (based on debian:bookworm-slim) and installs language runtimes via mise. The builder stage compiles both the main sandbox binary and the gocacheprog helper. See Dockerfile for details.
The nsjail Docker base image is built from the codize-dev/nsjail repository and published to ghcr.io/codize-dev/nsjail.
Comprehensive nsjail reference documentation lives in .context/docs/nsjail/. Covers execution modes, namespaces, filesystem isolation, networking, Seccomp-BPF, Kafel policy language, cgroups, resource limits, security features, process lifecycle, configuration reference, and CLI reference. Consult these docs when modifying nsjail settings or hardening the sandbox.
Request (runtime is required, must be "node", "node-typescript", "ruby", "go", "python", "rust", or "bash"):
{"runtime": "node", "files": [{"name": "index.js", "content": "console.log('hello');"}]}Optional stdin payload (delivered to the run-step child process only; compile steps never receive stdin):
{"runtime": "node", "files": [{"name": "index.js", "content": "..."}], "stdin": {"content": "hello\n", "base64_encoded": false}}stdin.content (required when stdin is present) is plain text by default. stdin.base64_encoded (optional, default false) treats content as a Base64-encoded string and decodes it server-side. Omit the top-level stdin to leave the child's stdin empty (read returns EOF immediately).
Response:
{"compile": null, "run": {"stdout": "<base64>", "stderr": "<base64>", "output": "<base64>", "exit_code": 0, "status": "OK", "signal": null, "duration_ms": 42}}Possible status values: "OK", "SIGNAL", "TIMEOUT", "OUTPUT_LIMIT_EXCEEDED".
compile: Compilation step result (same schema as run). null for non-compiled runtimes (node, ruby, python, bash). When compilation fails, run is null.
duration_ms (int64): wall-clock time in milliseconds between cmd.Start() and cmd.Wait() for that step's nsjail process. Always present for all statuses. Includes nsjail startup/teardown overhead.
files[].base64_encoded (bool, optional, default: false): when true, content is treated as a Base64-encoded string and decoded by the server. When false (default), content is used as plain text.