Which should you use, nsjail or Firecracker?
It depends on whether you know the code before it runs. For model output and agents with a shell, Runtime runs the Firecracker side as a service: a microVM per sandbox. Pick nsjail for known programs on your own Linux hosts, such as judge systems, CTF services and fuzzing targets. It needs no KVM and starts a process, not a machine. Many setups use both: they jail the monitor and run the code in the VM.
How nsjail works
nsjail is a "Linux process isolation tool using namespaces, resource limits, and seccomp-bpf syscall filters," published in Google's GitHub organization under Apache 2.0. Its README ends: "This is not an official Google product."
It builds a jail out of kernel features:
- Namespaces: UTS, mount, PID, IPC, network, user, cgroup and time.
- Filesystem:
chroot()orpivot_root(), read-only bind mounts, a custom/procand tmpfs. - Limits: wall time (600 seconds by default), CPU time, address space, open files and process count, plus cgroup v1 or v2 limits on memory, PIDs and CPU.
- System calls: seccomp-bpf policies written in Kafel, such as
ALLOW { read, write, exit, exit_group } DEFAULT KILL. - Network: an empty network namespace, a cloned MACVLAN interface, or userland networking through pasta.
It runs a program once, re-runs it in a loop for fuzzing, or listens on a TCP port and jails a fresh process per connection. Its README names CTF challenge hosting, fuzzing and desktop application sandboxing as uses. A typical run:
Terminal./nsjail -Mo --chroot / --user 99999 --group 99999 \ --time_limit 10 --rlimit_as 512 -- /usr/bin/python3 answer.pyEverything inside the jail still runs on the host's kernel. The namespaces change what the process can see. The seccomp policy limits which system calls it can make. The calls it is allowed to make are handled by the same kernel as every other process on the machine.
How Firecracker works
Firecracker is a virtual machine monitor. It asks the host's KVM for a virtual machine and boots a guest Linux kernel in it. The code's system calls go to that guest kernel. What reaches the host is the monitor's small device model, "only 5 emulated devices" in the project's count, and its own filtered system calls.
Firecracker also uses the same tools as nsjail, one level out. Its jailer "sets up system resources that require elevated permissions (e.g., cgroup, chroot), drops privileges, and then exec()s into the Firecracker binary." The monitor then runs with per-thread seccomp filters. The design document assumes "all vCPU threads are considered to be running malicious code as soon as they have been started."
Side by side
| nsjail | Firecracker | |
|---|---|---|
| What it is | A process jail | A virtual machine monitor for microVMs |
| Kernel the code uses | The host's | Its own guest kernel |
| Boundary | Namespaces, rlimits, cgroups, seccomp-bpf | Hardware virtualization, then a jailer and seccomp on the VMM |
| Host surface | Every system call the policy allows | 5 emulated devices, in the project's count, and the VMM's calls |
| Root inside | Root in a user namespace, on the host kernel | Root in its own machine |
| Input | A host directory or / as the root filesystem |
A kernel image and a root filesystem image |
| Network | Empty namespace, MACVLAN or pasta | virtio-net; filtering is left to the host |
| Needs | User namespaces enabled, or root | KVM on the host |
| Start time | No figure published | Up to 125 ms to guest init, in its specification |
| Memory overhead | No figure published | Up to 5 MiB for the VMM threads, in its specification |
| Language, licence | C++, Apache 2.0 | Rust, Apache 2.0 |
Is nsjail enough for LLM-generated code?
It narrows the attack surface a great deal, and a tight seccomp policy can make
a kernel bug hard to reach. The limit is the one every process jail shares.
The code runs on the host kernel, so one exploitable bug in a system call the
policy allows reaches the whole machine. General code needs many system calls.
A policy loose enough for Python, pip and a compiler leaves much of the
kernel reachable.
A microVM moves the boundary. The code can have root and every system call, because a kernel that belongs to a disposable machine answers them. Reaching the host means breaking that kernel and then the monitor.
Firecracker does not do everything for you either. Its design document says it "does not perform any network traffic filtering" and that guest traffic "should be filtered at the host-level." A production service still needs that layer, plus leases, quotas and cleanup.
When each fits
- Pick nsjail for known programs on your own Linux hosts: judge systems for a fixed compiler and test set, CTF services, fuzzing targets, and sandboxing desktop applications. It needs no KVM and starts a process, not a machine.
- Pick Firecracker for code you cannot predict: model output, agents with a shell, user-submitted projects that install their own packages, or anything that wants root.
- Use both as Firecracker itself does: jail the monitor process, and put the untrusted code inside the VM.
Runtime Cloud
Runtime puts each sandbox in its own Firecracker microVM, and runs the host side for you. Network, CPU, memory and cost are enforced on the host.
Runtime operates the host side: a Firecracker microVM per sandbox on dedicated servers, and the network filtering Firecracker leaves to the host. Every outbound connection goes through a proxy on the host. Private and internal addresses are refused, and root in the sandbox cannot change the rules (security).
TypeScriptimport { Sandbox } from "withruntime";const code = "import os; print(os.getuid(), os.uname().release)"; // what the model wroteawait using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 120, onLeaseEnd: "stop",});await sbx.files.write("/workspace/answer.py", code);const run = await sbx.exec(["sudo", "python3", "answer.py"], { timeoutMs: 10_000 });console.log(run.exitCode, run.timedOut, run.stdout); // root, in its own kernelPythonfrom withruntime import Sandboxcode = "import os; print(os.getuid(), os.uname().release)" # what the model wrotewith Sandbox.create( network={"internet": False}, timeout_seconds=120, on_lease_end="stop",) as sbx: sbx.files.write("/workspace/answer.py", code) run = sbx.exec(["sudo", "python3", "answer.py"], timeout_ms=10_000) print(run.exit_code, run.timed_out, run.stdout) # root, in its own kernelThe command timeout and the network switch do the work of nsjail's
--time_limit and empty network namespace. The network rules and the lease
are enforced on the host, outside the guest.
More: run untrusted LLM code, grade student code, what is egress control?, Firecracker vs gVisor.
A new account gets 100 free hours of a 1 GB machine in its first 30 days, with no card:
Terminalnpx withruntime sandboxes run -- sudo idTo run your own untrusted code this way, sign in with npx withruntime login
and use the free usage. Code already on another sandbox moves with the
migration guide.
Sources
Checked 26 October 2026.
Facts on this page were checked on 26 October 2026.