/docs/how-sandboxing-works

How sandboxing works

If you have never thought about sandboxes before, read this page first. It says what a sandbox is, sets out the layers of isolation you can choose between, explains what happens inside the kernel when a process is sandboxed, and says why nd7 picked the layer it did and what that costs. Nothing here is specific to nd7 until the last two sections.

A sandbox is a boundary, not a question#

When a coding agent wants to delete a directory or send a request to a host, there are two ways to keep that in check.

The first is a permission prompt: the agent stops, a dialog appears, and a person says yes or no. It is a decision made by a human being, once per action, and it has two well-known problems. It is slow, so people either stop using the agent or start clicking yes without reading. And it covers only the actions the agent's own code chose to ask about — a command the agent runs may start other programs that nobody was asked about.

The second is a sandbox: before the agent starts, the operating system is handed a set of rules, and from that moment the kernel — the part of the operating system that actually performs every file, network and process operation — refuses anything the rules do not allow. No one is asked. The refusal is an ordinary error, the same one a program gets when it lacks permission for a file. Crucially, the rules apply to the process and to every process it starts, because the kernel is checking operations rather than trusting a program to check itself.

The two are not rivals. A prompt is about intent, and stops an action a person would not want. A sandbox is about capability, and stops an action whatever the reason for it. A prompt can be talked round; a boundary in the kernel cannot.

The layers you can choose from#

"Sandbox" covers several mechanisms at different depths in the system, from the cheapest and most permeable to the strongest and most separate:

process sandbox — macOS Seatbelt; Linux Landlock plus seccomp; bubblewrap#

  • what isolates: the host kernel checks each operation the process makes against a rule set attached to that process
  • what it shares with the host: everything: the same kernel, the same filesystem, the same network stack, the same user account
  • typical start-up cost: not measured here
  • protects against: reading and writing the wrong files, reaching the wrong hosts or ports, in the process and all its children
  • does not protect against: a bug in the host kernel; anything the rules do allow

container — Linux namespaces and cgroups, for example Docker#

  • what isolates: namespaces give the process its own view of the filesystem, process table, network and users; cgroups cap what it can consume
  • what it shares with the host: the host kernel
  • typical start-up cost: not measured here
  • protects against: a process seeing or disturbing the rest of the machine (Docker: it "cannot see, and even less affect, processes running in another container, or in the host system"), and exhausting memory or CPU
  • does not protect against: a bug in the host kernel, which every container still calls into directly

application kernel — gVisor#

  • what isolates: a user-space program, the Sentry, implements the system call interface itself (gVisor: "No system call is passed through directly to the host")
  • what it shares with the host: the host kernel, but only through the narrow set of calls the Sentry itself makes
  • typical start-up cost: not measured here
  • protects against: exploitation of host kernel bugs by untrusted code, its stated purpose
  • does not protect against: bugs in the Sentry; and the same guide notes the interception cost falls mainly on system-call-bound workloads

microVM — Firecracker, Apple's Virtualization framework#

  • what isolates: hardware virtualization: its own guest kernel, with the hypervisor as the boundary
  • what it shares with the host: nothing but the physical machine and the hypervisor
  • typical start-up cost: Firecracker's specification: "<= 125 ms" from the InstanceStart call "to the start of the Linux guest user-space /sbin/init process"
  • protects against: everything above, including host kernel bugs; Firecracker "can safely run workloads from different customers on the same machine"
  • does not protect against: a bug in the hypervisor; and it is a different machine, so your tools and credentials are not in it unless you put them there

cloud sandbox service — a container or microVM on a provider's machine, for example E2B#

  • what isolates: the same mechanism, on someone else's hardware (E2B: "Isolation is at the hypervisor boundary, not the container or process boundary")
  • what it shares with the host: nothing on your machine
  • typical start-up cost: not stated in the documentation read
  • protects against: anything the agent does reaching your machine at all
  • does not protect against: your code being on a third party's infrastructure, and the round trip your files make

These dimensions trade off against each other. Every step down the list buys a stronger boundary and pays for it by sharing less with the host — and "sharing with the host" is another way of saying "being the environment you actually work in".

What happens inside the kernel#

Process sandboxing is worth understanding mechanically, because nd7 is built on it. It is three steps.

The process hands the kernel a set of rules. On macOS this is a Seatbelt profile, a text document in a Lisp-like language listing allowed and denied operations. On Linux it is a Landlock ruleset over the filesystem, usually paired with a seccomp filter, a small program that decides which system calls are permitted at all. Either way the process installs the rules on itself through an ordinary system call: no privileges, nothing running in the background.

The kernel checks every operation against them. From that call onwards, each attempt to open a file, connect to a host or start a program is matched against the rules before it is carried out. A refusal comes back as EPERMerrno(3) lists it as "Operation not permitted" — which every program already knows how to report.

Children inherit the rules, and nothing can loosen them. This is the property that makes the whole thing worth doing, and both kernels state it plainly. Landlock: "Once a thread is landlocked, there is no way to remove its security policy; only adding more restrictions is allowed", and "Every new thread resulting from clone(2) inherits Landlock domain restrictions from its parent". Seccomp: "If fork/clone and execve are allowed by @prog, any child processes will be constrained to the same filters and system call ABI as the parent."

macOS behaves the same way, and nd7 measured it rather than assuming it. ADR-0007 records that a confined process attempting to apply a different profile fails with EPERM — "looser, tighter, a superset, a subset, or the same rules with other parameters all fail"; only a semantically identical profile is accepted, as a no-op.

So a sandboxed agent cannot widen its own boundary, and neither can a command it runs, a script that command downloads, or a dependency with a compromised install step. The rules can only change from a process that was never inside them.

Six nested boxes, outside in: hardware, host kernel, an optional microVM guest kernel, a container, a process sandbox, and the agent with every process it starts. The process sandbox layer is highlighted, and an arrow marks it as the layer at which nd7 applies its rules. layers of isolation, outside in hardware host kernel microVM guest kernel (optional) container: namespaces, cgroups process sandbox: Seatbelt, Landlock the agent, and every process it starts nd7 applies its rules at this layer

Each box is enforced by the box outside it; the further in a layer sits, the less of your machine is inside it with the agent.

Why nd7 sits at the process-sandbox layer#

nd7's goal is an agent that works on your real checkout, with your real toolchain, and leaves a record of what it did. That goal picks the layer almost by itself.

The agent keeps the developer's environment. ADR-0007 weighed a Linux microVM and rejected it in one sentence: "More control and no nesting problem, at the cost of the developer's environment. It is Tier 2, not a replacement." A microVM is a different machine, and getting your repository, toolchains, configuration and credentials into it and the results back out is the work that layer costs you. A process sandbox costs none of it.

No privileges are needed, because installing a profile is a system call the process makes on itself: no root, no kernel module, no daemon, no entitlement. And it starts instantly, because there is no image to pull, no guest kernel to boot and no filesystem to copy.

The trade-offs are real and worth stating.

  • Kernel bugs are in scope. The agent calls straight into the same kernel you do. A flaw in its permission checks is a way out, and nothing at this layer can help; gVisor and microVMs exist because of exactly this.
  • The host is shared. Same kernel, same network stack, same user account. What the rules allow, the agent gets on your real machine — so the rules are the whole of the protection, which is why the exact rule set is written out rather than summarised.
  • It is per-process, not per-machine. Anything already running outside the session is outside the boundary.

If the agent's code must not touch your machine at all, a microVM or a cloud sandbox is the right answer and nd7 is not. How nd7 compares to other ways of sandboxing a coding agent puts the options side by side.

Further reading#

Each of these is the primary source for a claim on this page.

  • App Sandbox — Apple's developer reference for the supported way a macOS application confines itself.
  • Apple Platform Security: security of runtime process — what sandboxing is for: "Sandboxing is designed to prevent apps from gathering or modifying information stored by other apps."
  • sandbox-exec(1) — a mirror of the macOS man page, which Apple no longer publishes; it describes the command that "enters a sandbox using a profile", and marks it DEPRECATED.
  • sandbox_init(3) — a mirror of the man page for the C function that applies a profile, also marked DEPRECATED.
  • Endpoint Security — Apple's reference for the framework nd7 plans to use to record what the kernel observed; see Roadmap.
  • Landlock — the Linux kernel's unprivileged sandbox, including the rule that a policy can only ever be tightened.
  • Seccomp BPF — filtering system calls, and how filters pass to child processes.
  • Docker security — what namespaces and cgroups isolate, and what they do not.
  • gVisor architecture guide: security — what an application kernel is, and why no system call reaches the host.
  • Firecracker design — how a microVM is built out of KVM, seccomp filters, cgroups and a privilege-dropping jailer.
  • bubblewrap — the "low-level unprivileged sandboxing tool used by Flatpak and similar projects", which Claude Code's Linux sandbox is built on.

Updated . This page as Markdown · Source on GitHub.