Step 1
Out of the sandbox
Namespaces, Landlock, a system call filter and the limits, built in the guest by the same driver that builds them on a machine without one.
Isolation
Every other harness that sandboxes at all shells out to bubblewrap, Docker or a platform sandbox. Chock builds its confinement in the process itself, and the microVM goes underneath that rather than in place of it. A tool call runs in a guest with a kernel of its own, and the namespaces, the filter, Landlock and the limits are built in there, by the same driver from the same configuration.
There is a sandbox on each side of the guest. One inside it, holding the tool call, and one around the process that runs the guest, built from a much shorter list because that process runs a machine rather than your code.
around the process that runs the guest
a kernel of its own
built by the same driver from the same configuration
no privilege in the guest
a throwaway copy of your project
the filesystem, held by the guest kernel
31 calls killed, 3 refused by argument
your tool call, in a copy of your project
Three boundaries. The rings inside one are what that boundary is made of, not a count of its own.
A reader's first assumption is that a virtual machine stands in for the sandbox. It does not. The guest is a second kernel, so every layer that holds a tool call without one holds it in there too, and getting out of that sandbox leaves you inside the guest, which is itself inside a sandbox.
Step 1
Namespaces, Landlock, a system call filter and the limits, built in the guest by the same driver that builds them on a machine without one.
Step 2
Through the virtio and KVM interface, held by a kernel of its own. A fault here is a different class from a Landlock or seccomp bypass, and that is the step worth having.
Step 3
The process holding the guest is itself confined, and its allow list is far smaller than a tool call's, because it runs a machine rather than your code.
What is not independent of what
The first step and the third use the same two mechanisms, seccomp and Landlock, so a single technique against either one could apply at both ends. They are not independent of each other and we would rather say so than count three layers. The middle step is the one that stands on its own, because a virtio or hypervisor fault is a different class of bug held by a different kernel.
Every layer of a sandbox is a judgement your own kernel makes about a process it is running. Namespaces, Landlock and the system call filter are all enforced by the kernel a tool call is already sitting on, so a fault in a call the filter permits is a fault in the kernel running your laptop. A guest moves that work onto a kernel of its own, and gives the agent one we are prepared to lose.
It also levels the platforms. Seatbelt is to the Darwin driver what Landlock and seccomp together are to the Linux one, but the native Darwin driver expresses none of the five capabilities the sandbox names: no bind mount, no capped area, no procfs, no cgroup placement, no device passthrough.
A Darwin guest is a Linux guest, running the same driver from the same configuration, so inside the guest the layers are identical wherever you work. The sandbox around the host process is not identical: seccomp and Landlock on Linux, a Seatbelt profile on macOS. Same intent, different mechanism.
A harness that shells out has a dependency that can be absent, and the usual answer to a missing one is to run without it.
There is a ceiling as well. bubblewrap takes the whole mount set on its command line, and a Nix closure is thousands of paths: Codex hit Exceeded maximum number of arguments that way, and Claude Code hit the 128 KB argument limit. Chock builds the namespaces itself, so there is no command line to overflow and nothing to be missing.
Starting a helper runs the dynamic loader before the helper itself, and the loader reads the environment, so LD_PRELOAD and its relatives sit on the path that is meant to be building your boundary.
It is work as well as surface: finding the program, reading it, the kernel's own checks, then every shared object mapped before a line of it runs. Chock never leaves the process to build a boundary. It forks, installs the layers in the child, and execs your command once they are on.
A confinement has to be installed before an exec, so a monitor started as a program has to be granted everything it will ever open: /dev/kvm, the kernel, the initrd, its own libraries. Chock opens all of that first and confines afterwards, with no exec to come, so the grant is the share set and nothing more.
It is also what carries the network across. The guest writes a name and a descriptor comes back already open, on the library's own message type. Hand a guest a tap device instead and it resolves and connects for itself.
Every other harness that sandboxes at all shells out to bubblewrap or to the platform's own sandbox, around the commands an agent runs. Read at the commit beside each name, on the date beside that.
Built in the process itself, then a microVM guest with a sandbox on each side of it.
bubblewrap on Linux and Seatbelt on macOS, around shell commands, and off until you turn it on.
bubblewrap on Linux and Seatbelt on macOS, around shell commands.
bubblewrap on Linux, around the terminal and fetch.
Nothing.
Nothing.
It resolves no name either. The guest writes a name, the host decides whether the policy permits it, resolves it and connects, and hands back a descriptor that is already open. So a guest cannot ask for one host and be handed another, and a name it was refused never reaches a resolver at all.
A refusal arrives inside the guest as nothing more than a stream that ended.
# the guest asks for a name$ connect api.example.com:443# the host decides, resolves, connects# and hands back a descriptor already open# a refusal arrives as a stream that endedFalling back to the driver underneath would confine a session more weakly than the operator asked for, with nothing in the log saying so. There is no fallback here, the same way there is no switch: a layer that cannot be applied fails the tool call, and a guest that cannot be started fails it too.