Cellblock
A minimal Linux sandbox runner written in Zig that executes untrusted binaries under layered, default-deny isolation, built for reproducing proofs of concept safely.
Case study
Zig ยท Linux isolationOverview
Built Cellblock, a small static binary that runs an untrusted program inside layered kernel isolation: user and mount namespaces, a seccomp-bpf syscall allowlist, Landlock filesystem rules and cgroup v2 resource limits. Behaviour is described by a short declarative policy file instead of a long list of flags.
Problem
Reproducing a vulnerability or analysing a suspicious binary means running code you do not trust. A full VM is heavy for a quick reproduction loop, and existing wrappers are either large, depend on setuid helpers, or expose isolation as flags that are easy to get subtly wrong.
Constraints
- Default-deny: nothing is permitted unless the policy says so
- Single static binary with no runtime dependencies, so it works on a minimal or air-gapped host
- No setuid helpers; runs fully unprivileged using user namespaces
- Policy must be readable and reviewable at a glance
Approach
Written in Zig for explicit allocation, a small static footprint and direct syscall access without a libc dependency. The runner builds the sandbox in a fixed, audited order (namespaces, then mounts, then Landlock, then seccomp last) so each layer is in place before the target ever executes. The policy file is parsed and validated up front, and invalid or contradictory rules abort the run instead of silently weakening isolation.
Key decisions
Layered kernel primitives instead of a VM
Reasoning: Namespaces, seccomp, Landlock and cgroups give isolation that starts in milliseconds and keeps the reproduction loop fast. Each layer limits a different thing, so a gap in one does not expose everything.
Alternatives considered:
- Full virtual machine per run (strong, but slow to start and heavy to manage)
- Thin wrapper over an existing tool (inherits its defaults and its attack surface)
Declarative, default-deny policy
Reasoning: A policy file that lists what is allowed is easy to review and diff. Anything not listed is blocked, which makes mistakes fail closed.
Alternatives considered:
- Command-line flags (easy to misconfigure, hard to review)
- Allow-by-default with a blocklist (fails open)
Tech stack
Result & impact
- Footprint
- Single static binary, no runtime dependencies
- Isolation layers
- Namespaces, seccomp-bpf, Landlock, cgroups v2
- Policy model
- Declarative, default-deny
- Status
- Prototype in active development
Cellblock gives me a fast, disposable place to run a proof of concept with a known and reviewable boundary. It supports the isolation side of my research and the 'smallest reproduction in an isolated environment' approach described on the About page.
Learnings
- Ordering matters: applying seccomp too early breaks setup, and applying it too late leaves a window, so the sequence has to be deliberate and tested.
- Landlock and seccomp complement each other; neither is a complete boundary alone.
- Failing closed on bad policy is worth the occasional annoyance.
Additional context
Isolation is only useful if you know exactly what it separates. Cellblock is as much a way of testing that question as a tool: its policy format makes the boundary explicit, so it can be reasoned about, reviewed and attacked.
It is deliberately not a container runtime. It does one job, running one untrusted process under a stated policy, and keeps the trusted code small enough to read in a sitting.