An MVP implementation of an isolated code execution service that provides secure code execution capabilities. The API accepts untrusted code for Python, Node.js, Ruby, PHP, and Go, executes it inside hardened containers, and returns structured results including stdout/stderr streams and signed artifact URLs.
- REST API with OpenAI-style
/v1/runsand/v1/filesendpoints - Per-language runner containers with network isolation, non-root execution, and seccomp/AppArmor profiles
- Strict resource limits (CPU, wall clock, memory, output, and artifact caps)
- Local artifact storage with HMAC-signed, time-limited download URLs
- Static bearer-token authentication with per-key token bucket rate limiting
- Structured JSON logging and simple usage metrics scaffolding
- Jest unit and integration tests covering success, timeout, OOM, and artifact flows
- Docker Compose stack for local development with one image per language
- Minimal admin UI for manual run submission
- Open-WebUI Integration: Use as a code execution tool in AI chat interfaces
-
Install prerequisites
- Docker and Docker Compose
- Node.js 20+ (for local development)
-
Run the test suite
make test -
Bring the stack up
make up
The API listens on
http://localhost:8080. A development bearer tokendev_123is preconfigured. The admin UI is served at the root: openhttp://localhost:8080/.
-
Submit a run
curl -s -X POST http://localhost:8080/v1/runs \ -H "Authorization: Bearer dev_123" \ -H "Content-Type: application/json" \ -d '{ "language": "python", "code": "print(1+1)", "limits": {"timeout_ms": 3000} }'
Language values must be one of:
python,node,ruby,php,go(usenode, notnode.js). -
Tear down
make down
Key environment variables for the API container:
| Variable | Description |
|---|---|
PORT |
HTTP listen port (default 8080) |
API_KEYS |
Comma-separated list of token:label:rps:burst entries |
SANDBOX_WORKDIR |
Host path for per-run sandboxes (bind-mounted read/write) |
STORAGE_DIR |
Artifact storage directory |
PUBLIC_BASE_URL |
Base URL used when generating signed artifact links |
SIGNING_KEY |
HMAC signing secret for download URLs |
SECCOMP_PROFILE |
Path to seccomp JSON profile mounted inside the container |
APPARMOR_PROFILE |
Optional AppArmor profile name applied to runner containers |
RUNNER_IMAGE_PYTHON etc. |
Override runner images (defaults to code-executor-runner-*:latest) |
HOST_SANDBOX_DIR |
Host directory used by the Docker runner for --mount src=... (binds the same location as SANDBOX_WORKDIR inside the API container) |
DISABLE_SANDBOX_SECURITY |
When set to 1, omits seccomp/AppArmor and no-new-privileges flags (useful on Docker Desktop/macOS) |
The orchestrator launches runner containers via the Docker CLI. The Compose file builds the runner images and exposes them for reuse, but the API executes code by spawning ephemeral containers with --network=none, --read-only, --cap-drop=ALL, --pids-limit=32, and the provided seccomp/AppArmor policies. On Docker Desktop/macOS, the default Compose config sets DISABLE_SANDBOX_SECURITY=1 to relax those flags for compatibility.
- Adversary: Any API client supplying arbitrary code or uploaded files.
- Assets: Host integrity, other tenants' data, and service availability.
- Attacker goals:
- Break out of the sandbox to access host resources or the Docker daemon
- Exfiltrate sensitive data over the network
- Launch denial-of-service attacks via resource exhaustion
- Mitigations:
- Containers run as an unprivileged UID/GID with
no-new-privileges - Network disabled (
--network=none) and AppArmor policy forbids socket operations - Seccomp allowlist blocks dangerous syscalls (mount, ptrace, bpf, etc.)
- Read-only root filesystem with writable
/worktmpfs per run - CPU/memory/timeout limits enforced via Docker and in-runner rlimits
- Artifact allowlist restricts output to
/work/outputs - Token bucket rate limiting per API key prevents brute-force and DoS
- Uploaded file size caps and environment variable sanitisation reduce attack surface
- Containers run as an unprivileged UID/GID with
/api # TypeScript API service
/runners # Language-specific sandbox images
/seccomp # Seccomp profile allowlists
/apparmor # AppArmor profile
/web/admin # Static admin page for manual runs
See api/openapi/spec.yaml for the full REST schema.
- Unit and integration tests run under Jest without touching Docker by using a mock sandbox runner.
- The Docker sandbox adapter uses
docker runwith ephemeral containers; ensure the API container has permission to invoke the Docker daemon or replace the adapter with containerd/nsjail integration. - The runner entrypoints enforce output caps and write usage metrics (
usage.json) consumed by the orchestrator. - The static admin page posts directly to the API using the configured bearer token.
-
API TypeScript edits (
api/src/...):docker compose --profile runners build api && docker compose --profile runners up -d # or: docker compose up -d --build api
-
Runner entrypoint edits (
runners/{python|node|ruby|php|go}/entrypoint.{sh|py}):- Single runner:
docker compose --profile runners build runner-node && docker compose --profile runners up -d - Build multiple runners at once (example: php + node + ruby):
docker compose --profile runners build runner-php runner-node runner-ruby && docker compose --profile runners up -d
- Single runner:
-
Web UI edits (
web/admin/index.html): just refresh the browser (bind-mounted). -
Compose/Dockerfile/env changes:
docker compose --profile runners up -d --build
-
Quick restart without rebuild:
docker compose restart api
-
Stop everything:
make down # or docker compose down --remove-orphans && docker compose --profile runners down --remove-orphans
This API can be integrated with Open-WebUI to provide code execution capabilities in AI chat interfaces.
Quick Setup:
- Start the API:
make up - Import
openwebui_tool.pyinto Open-WebUI's Tools section - Use
host.docker.internal:8080for the api_url in valve settings if Open-WebUI is in Docker, or just importopenwebui_tool_docker.pyinstead ofopenwebui_tool.py
For detailed instructions, see Open-WebUI Integration Guide.
MIT