home / projects / cellblock

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 isolation

Overview

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

Zigseccomp-bpfLandlockLinux namespacescgroups v2

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.

All projects

Tags

#isolation#sandbox#zig#linux

Related

shares a tag