Sandboxed Code Execution Runner
Runs untrusted Python, JavaScript, and Bash inside a hardened jail capped at 5 seconds, 128 MB, and 0.5 CPU.
At a glance
- Per-run limits on CPU, memory, processes, and output size
- Python, JavaScript, and Bash runtimes
- Five-second wall-clock ceiling with forced termination
- 128 MB memory and 0.5-core CPU caps
- Queue and result store backed by Postgres and Redis
From the repository
README
Kernel-enforced execution of untrusted code — every payload jailed by cgroups v2, namespaces, seccomp-BPF and a read-only rootfs, and every run audited in PostgreSQL.
python · javascript · bash → 5-second, 128 MB, 0.5-core, 32-PID, 512 KB-output jail
Overview
A security-hardened execution service for code you do not trust. Callers submit source over a small REST API; the daemon spawns each payload into an ephemeral kernel jail where the Linux kernel itself — not application logic — guarantees containment:
- the process cannot escape the filesystem (
pivot_rootonto a read-only rootfs), - it cannot talk to the network or the host (private namespaces +
lodown + seccomp killssocket), - it cannot exhaust the machine (cgroups v2 memory/CPU/PID ceilings),
- it cannot outlive its budget (5 s wall-clock watchdog), and
- every outcome — including violations — lands in an immutable audit trail.
Key Features
- 🧱 Defense in depth — six Linux namespaces (
PID · MOUNT · NET · IPC · UTS · USER), a strict seccomp-BPF allowlist,cgroups v2resource jails, and a read-onlypivot_rootfilesystem with a 16 MB tmpfs scratch space. - ⚡ Sub-second starts — a warm pool of 16 execution slots with a 3-second acquire timeout avoids per-request cold-start cost while bounding concurrency.
- ⏱️ Deterministic limits — hard
5 swall-clock timeout,128 MBmemory ceiling (swap disabled, instant OOM kill),0.5CPU core,pids.max = 32fork-bomb immunity, and512 KBoutput caps on stdout/stderr. - 📜 Full execution audit — exit code, wall time, peak memory, CPU time, truncation flags, and security events are written asynchronously to PostgreSQL (
execution_runs). - 🧪 Attack-grade test suite — dedicated integration tests for fork bombs, network escape, memory OOM, timeout watchdog, and concurrency stress, runnable inside a privileged container against the real kernel.
System Architecture
Component Breakdown
| Component | Responsibility | Source |
|---|---|---|
| Ingestion Daemon | REST surface: POST /api/v1/execute + /health; request validation; async audit emission | cmd/server/main.go |
| Warm Runner Pool | Semaphore-bounded execution slots (16) with a 3 s acquire timeout → 503-style exhaustion backpressure | internal/runner/pool.go |
| Process Runner | Builds the language command, attaches the cgroup, streams pipes through truncating writers, enforces the 5 s watchdog, harvests cgroup metrics, classifies results | internal/runner/process.go |
| Kernel Isolation | cgroups v2 limiter (memory.max, cpu.max, pids.max), namespace flags (CLONE_NEWPID/NET/NS/IPC/UTS/USER), seccomp-BPF filter compiler | internal/isolation/ |
| sandbox_init | Child trampoline: read-only pivot_root + tmpfs /dev//tmp, network detachment (lo down), seccomp apply, then syscall.Exec of the target | cmd/sandbox_init/main.go |
| Untrusted Code | The guest payload. Host devices stripped, /proc hidden (hidepid=2), all non-allowlisted syscalls killed | runtimes/ rootfs images |
| PostgreSQL 16 | Durable audit log of every run, including security violations | migrations/001_init_sandbox_schema.sql |
| Redis 7 | sandbox:jobs queue + BRPop worker draining into the pool (module implemented, not yet wired into the daemon — see Known Limitations) | internal/queue/redis_consumer.go |
Trust Boundaries
| Boundary | What it separates | Enforcement |
|---|---|---|
| Client ↔ Daemon | Unauthenticated payload submission from control plane | schema validation (tenant_id, language, code) |
| Control plane ↔ Jail | Trusted Go code vs. hostile payload | fork into private namespaces; pivot_root; seccomp inherit-on-exec |
| Jail ↔ Kernel limits | Guest resource consumption vs. host capacity | cgroups v2 memory.max=128MB, swap.max=0, cpu.max="50000 100000", pids.max=32 |
| Jail ↔ Network | Any egress or host-side reachability | private NET namespace, lo down, socket syscall killed |
🔍 An interactive, theme-aware version of this diagram (with PNG/SVG export) is authored from the evidence-backed candidates in
docs/archify/.
Execution Lifecycle — Request to Audit
Step-by-step:
- Submit — client
POSTs JSON (tenant_id,language,code, optionalstdin/limits). Malformed or unsupported payloads fail fast with400/422. - Acquire — the handler requests a slot from the warm pool; after 3 s of contention the request is rejected rather than queued unboundedly.
- Jail — the runner creates an ephemeral cgroup v2 scope, forks with all six isolation namespace flags, and attaches the child PID before it can allocate.
- Lock down —
sandbox_initpivots into the read-only rootfs, mounts quota'd tmpfs, tears downlo, installs the seccomp-BPF allowlist, thenexecs the interpreter — the payload never runs privileged by the parent. - Contain — infinite loops die at the 5 s watchdog; allocation storms trip
memory.max(clean OOM kill); fork bombs are refused atpids.max = 32;socket/ptrace/mountcalls are killed by the kernel (SIGSYS/SIGKILL →SECURITY_VIOLATION). - Report — capped stdout/stderr, exit code, wall time, peak memory and CPU are classified into one of six statuses and returned as JSON.
- Audit — the result is inserted into
execution_runsfrom a detached goroutine with its own 2 s budget, so persistence can never block or leak into response latency.
Tech Stack
| Layer | Technology | Role |
|---|---|---|
| Language | Go 1.27 (github.com/kavaryx/sandboxed-runner) | daemon, runner, isolation, tests |
| Kernel isolation | cgroups v2 · namespaces · seccomp-BPF (libseccomp-golang) · pivot_root | containment enforcement |
| Runtime images | minimal Python / Node rootfs (Docker-built, runtimes/*.tar) | read-only guest filesystems |
| API | net/http stdlib (REST, JSON) | ingestion + health |
| Queue | Redis 7 (go-redis/v9) — LPUSH/BRPop on sandbox:jobs | async job pipeline (module) |
| Persistence | PostgreSQL 16 (lib/pq) | durable execution audit |
| Tooling | Docker Compose · Makefile · go test | dev, build, security testing |
Project Structure
.
├── cmd/
│ ├── server/ # REST ingestion daemon (main entrypoint)
│ └── sandbox_init/ # child trampoline: rootfs → network → seccomp → exec
├── internal/
│ ├── domain/ # ExecutionRequest / ExecutionResult + default limits
│ ├── isolation/ # cgroup.go · namespaces.go · seccomp.go · rootfs.go · network.go
│ ├── runner/ # process.go (watchdog) · pool.go (warm slots) · sanitizer.go (caps)
│ ├── queue/ # redis_consumer.go — LPUSH/BRPop worker module
│ └── repository/ # db.go · audit_repo.go — PostgreSQL audit persistence
├── migrations/ # 001_init_sandbox_schema.sql (execution_runs)
├── runtimes/ # per-language rootfs Dockerfiles + build_rootfs.sh
├── tests/ # fork_bomb · network_escape · memory_oom · timeout_watchdog · concurrency
├── docs/
│ └── archify/ # evidence-backed interactive diagram candidates
├── docker-compose.yml # postgres + redis + privileged sandbox-runner
├── Dockerfile.dev # toolchain image (libseccomp, cgroup-tools, iproute2…)
└── Makefile # build · test · test-security · rootfs · up · down
Quick Start
⚠️ Kernel enforcement (cgroups v2, namespaces, seccomp, pivot_root) requires Linux. On macOS/Windows the service compiles and runs with graceful degradation — use the provided container for real isolation.
1 · Clone & boot dependencies
git clone https://github.com/kavaryx/sandboxed-runner.git
cd sandboxed-runner
docker compose up -d postgres redis
2 · Configure
export PORT=8080
export POSTGRES_URL="postgres://sandbox:sandbox_secret@localhost:25432/sandbox_db?sslmode=disable"
export REDIS_URL="redis://localhost:26379/0"
# optional — enable the read-only guest filesystem (needs a Linux host):
export ROOTFS_DIR="runtimes/rootfs/python"
3 · Build & run
make build # -> bin/server + bin/sandbox_init
./bin/server
Full containerized path (real kernel jail)
make up # privileged runner + postgres + redis
docker compose exec sandbox-runner bash
root@sandbox-runner:/workspace# make build && ./bin/server
Smoke test
curl -s -X POST http://localhost:8080/api/v1/execute \
-H "Content-Type: application/json" \
-d '{"tenant_id":"demo","language":"python","code":"print(sum(range(10)))"}'
{
"id": "3f6c7d1e-…",
"tenant_id": "demo",
"language": "python",
"status": "SUCCESS",
"exit_code": 0,
"stdout": "45\n",
"stderr": "",
"stdout_truncated": false,
"stderr_truncated": false,
"wall_time_ms": 21,
"peak_memory_bytes": 8421376,
"cpu_time_user_ms": 15,
"cpu_time_system_ms": 6,
"created_at": "2026-10-12T12:00:00Z"
}
Execution statuses
| Status | Trigger |
|---|---|
SUCCESS | clean exit |
TIMEOUT | wall-clock watchdog kill (exit 137) |
OOM_KILLED | memory.max breach (memory.swap.max=0) |
FORK_BOMB_BLOCKED | pids.max refused fork |
SECURITY_VIOLATION | seccomp SIGSYS/SIGKILL (security_event populated) |
INTERNAL_ERROR | runner/daemon failure |
Default Resource Limits
| Limit | Default | Mechanism |
|---|---|---|
| Memory | 128 MB (+ swap banned) | cgroup memory.max / memory.swap.max |
| CPU | 0.5 core | cgroup cpu.max = "50000 100000" |
| Processes | 32 | cgroup pids.max (anti fork-bomb) |
| Wall clock | 5 s | context deadline → SIGKILL |
| Output | 512 KB per stream | truncating writers |
| Concurrency | 16 slots, 3 s acquire | semaphore pool |
Testing
make test # unit tests (no privileged ops needed)
make test-security # kernel-enforcement suite inside the container
make test-security runs the integration suite in tests/ against a real cgroups v2 host:
fork_bomb_test · network_escape_test · memory_oom_test · timeout_watchdog_test · concurrency_stress_test.
Known Limitations & Roadmap
Today (by design or not yet wired):
rust/cpass domain validation but have no exec mapping yet — planned via prebuilt artifacts inside compiled rootfs images.- The Redis worker (
internal/queue) is a complete module but is not yet wired into the daemon's request path. - The HTTP surface has no authentication/authorization —
tenant_idis audit attribution, not a security boundary. Do not expose beyond a trusted network. - Enforcement is Linux-only; cross-platform builds compile with no-op isolation shims (
*_other.go). docker-compose.ymlcredentials are dev-only placeholders.
Roadmap: queue-backed async executions → compile-language pipelines → API authn/authz + per-tenant quotas → gVisor/Wasm tier → metrics endpoint.
Reference Spec
The full design brief and invariants live in
developer_tools_untrusted_code_sandbox_autonomous_blueprint.md.
Built for security research. If you test an escape and it succeeds — that's a bug, and a PR with the reproducer is very welcome.