Kavryx Dev
← All products
Security & IsolationPROTOTYPE

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_root onto a read-only rootfs),
  • it cannot talk to the network or the host (private namespaces + lo down + seccomp kills socket),
  • 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 v2 resource jails, and a read-only pivot_root filesystem 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 s wall-clock timeout, 128 MB memory ceiling (swap disabled, instant OOM kill), 0.5 CPU core, pids.max = 32 fork-bomb immunity, and 512 KB output 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

Rendering diagram…

Component Breakdown

ComponentResponsibilitySource
Ingestion DaemonREST surface: POST /api/v1/execute + /health; request validation; async audit emissioncmd/server/main.go
Warm Runner PoolSemaphore-bounded execution slots (16) with a 3 s acquire timeout → 503-style exhaustion backpressureinternal/runner/pool.go
Process RunnerBuilds the language command, attaches the cgroup, streams pipes through truncating writers, enforces the 5 s watchdog, harvests cgroup metrics, classifies resultsinternal/runner/process.go
Kernel Isolationcgroups v2 limiter (memory.max, cpu.max, pids.max), namespace flags (CLONE_NEWPID/NET/NS/IPC/UTS/USER), seccomp-BPF filter compilerinternal/isolation/
sandbox_initChild trampoline: read-only pivot_root + tmpfs /dev//tmp, network detachment (lo down), seccomp apply, then syscall.Exec of the targetcmd/sandbox_init/main.go
Untrusted CodeThe guest payload. Host devices stripped, /proc hidden (hidepid=2), all non-allowlisted syscalls killedruntimes/ rootfs images
PostgreSQL 16Durable audit log of every run, including security violationsmigrations/001_init_sandbox_schema.sql
Redis 7sandbox: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

BoundaryWhat it separatesEnforcement
Client ↔ DaemonUnauthenticated payload submission from control planeschema validation (tenant_id, language, code)
Control plane ↔ JailTrusted Go code vs. hostile payloadfork into private namespaces; pivot_root; seccomp inherit-on-exec
Jail ↔ Kernel limitsGuest resource consumption vs. host capacitycgroups v2 memory.max=128MB, swap.max=0, cpu.max="50000 100000", pids.max=32
Jail ↔ NetworkAny egress or host-side reachabilityprivate 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

Rendering diagram…

Step-by-step:

  1. Submit — client POSTs JSON (tenant_id, language, code, optional stdin/limits). Malformed or unsupported payloads fail fast with 400/422.
  2. Acquire — the handler requests a slot from the warm pool; after 3 s of contention the request is rejected rather than queued unboundedly.
  3. 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.
  4. Lock down — sandbox_init pivots into the read-only rootfs, mounts quota'd tmpfs, tears down lo, installs the seccomp-BPF allowlist, then execs the interpreter — the payload never runs privileged by the parent.
  5. Contain — infinite loops die at the 5 s watchdog; allocation storms trip memory.max (clean OOM kill); fork bombs are refused at pids.max = 32; socket/ptrace/mount calls are killed by the kernel (SIGSYS/SIGKILL → SECURITY_VIOLATION).
  6. Report — capped stdout/stderr, exit code, wall time, peak memory and CPU are classified into one of six statuses and returned as JSON.
  7. Audit — the result is inserted into execution_runs from a detached goroutine with its own 2 s budget, so persistence can never block or leak into response latency.

Tech Stack

LayerTechnologyRole
LanguageGo 1.27 (github.com/kavaryx/sandboxed-runner)daemon, runner, isolation, tests
Kernel isolationcgroups v2 · namespaces · seccomp-BPF (libseccomp-golang) · pivot_rootcontainment enforcement
Runtime imagesminimal Python / Node rootfs (Docker-built, runtimes/*.tar)read-only guest filesystems
APInet/http stdlib (REST, JSON)ingestion + health
QueueRedis 7 (go-redis/v9) — LPUSH/BRPop on sandbox:jobsasync job pipeline (module)
PersistencePostgreSQL 16 (lib/pq)durable execution audit
ToolingDocker Compose · Makefile · go testdev, 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

StatusTrigger
SUCCESSclean exit
TIMEOUTwall-clock watchdog kill (exit 137)
OOM_KILLEDmemory.max breach (memory.swap.max=0)
FORK_BOMB_BLOCKEDpids.max refused fork
SECURITY_VIOLATIONseccomp SIGSYS/SIGKILL (security_event populated)
INTERNAL_ERRORrunner/daemon failure

Default Resource Limits

LimitDefaultMechanism
Memory128 MB (+ swap banned)cgroup memory.max / memory.swap.max
CPU0.5 corecgroup cpu.max = "50000 100000"
Processes32cgroup pids.max (anti fork-bomb)
Wall clock5 scontext deadline → SIGKILL
Output512 KB per streamtruncating writers
Concurrency16 slots, 3 s acquiresemaphore 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 / c pass 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_id is 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.yml credentials 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.