v0.8.0 Execution primitive - Node.js

Run untrusted code
in hardened containers.

A single-purpose Node.js library that runs your code in a container it tears down afterwards, and hands you back the exit code, logs, and any files you asked for. Nothing else.

Runtime
Node >=20
Dependency
1 tar only
Tarball
12.7 kB
Tests
49 / 49 pass
License
AGPL 3.0
A single glowing sphere floating above a wireframe cube on an isolated dark grid - the visual metaphor for a sandboxed container.
Fig. 01 - One process, one cell, one grid. light-runner / visual
what you get

Give it code. Get back an exit code, logs, and the files you asked for.

01 / isolated

Your code runs in its own cell.

Every run gets a fresh container, its own volume, its own network. Nothing leaks in from the host, nothing leaks out to sibling runs. Torn down on exit - success or not.

02 / drop in, pull out

Send a folder. Get any file back.

Point at a directory on your disk, it becomes the container's workdir. When the run finishes, ask for any path - a report, a binary, a whole build tree - and it lands back on your host.

03 / stop on demand

Cancel, abort, or time out.

Pass an AbortSignal, call cancel(), or set a deadline. The container dies and its volume goes with it. No zombie processes, no leaked disk.

04 / any stack

Python, Node, Go, Ruby, shell - anything.

Any Docker image on your registry or someone else's. No special runtime inside the container, no SDK to import, no convention to follow. Your code stays your code.

quick start

Five lines to run, one to get your artefact.

Point light-runner at an image and a folder. Pipe input through stdin. Extract whatever your container wrote.

No HTTP server. No config file. No CLI.

npm install light-runner
example.ts
import { DockerRunner } from 'light-runner';

const runner = new DockerRunner({ memory: '512m', cpus: '1' });

const execution = runner.run({
  image:   'python:3.12-alpine',
  command: 'python main.py',
  dir:     './my-project',
  input:   { task: 'compute', n: 20 },
  timeout: 30_000,
  extract: [{ from: '/app/result.json', to: './out' }],
});

const result = await execution.result;

result.success     // true if exit 0 and not cancelled
result.exitCode    // the container's exit code
result.extracted   // [{ from, to, status, bytes }]
security model

Hardened defaults, never opt-out. Add more restrictions, never fewer.

Kernel permissions
Dangerous capabilities stripped at startup - raw sockets, device creation, chroot escapes, capability juggling, audit-log spoofing. Every run, unconditionally.
No privilege escalation
A setuid binary inside your container cannot elevate above the user it starts as.
Fork-bomb protection
Max 100 processes per container. A runaway loop caps out in milliseconds instead of paging the host.
Memory and CPU budget
512 MiB and one core by default, cgroup-enforced. Noisy runs cannot starve their neighbours. Tunable per runner.
Network isolation
Isolated bridge by default with inter-container traffic blocked. Pass network: 'none' to sever it entirely.
Host filesystem protection
Symlinks in your input folder are filtered before seeding, so a stray link cannot reach back into the host filesystem.
Safe file extraction
A container cannot write outside the destination folder you chose: paths escaping upward (..) are refused. Each extracted entry is capped at 1 GiB so a runaway output cannot fill your disk.
Kernel-level hardening
Swap the runtime to gVisor with one option for user-space syscall interception, at ~10-30% I/O cost.
Does not cover - kernel exploits, runc CVEs, side-channel attacks. For genuinely hostile code, combine with { runtime: 'runsc' }.
ecosystem

Three tools. Each does one thing.

light-runner

Spawn one container, return exit code and files.

The execution primitive. Domain-agnostic. Zero orchestration. The other two tools in this family both call down to this one.

Stable - v0.8.0
light-run

CLI and HTTP surface around light-runner.

Point a POST endpoint at it, pipe bodies through, get results back. Stateless wrapper, same defaults, same guarantees.

Planned
light-process

DAG orchestration, retries, fan-out.

When one container is not enough. Composes runs into pipelines with backoff, concurrency limits, and structured outputs.

Planned