Filesystem Isolation Strategies in Code Execution Sandboxes
Layering filesystem controls stops AI-generated code from reaching files it shouldn't.

Filesystem isolation in a code execution sandbox works as a stack of controls: mount namespaces, chroot, overlay filesystems, virtual mounts, and permission layers like seccomp and MAC policies, each covering a gap the last one leaves open. Understanding where each layer stops, and what breaks when a workload leans on it alone, is the difference between a sandbox that looks secure in a design doc and one that survives contact with real AI agent traffic.
AI agents execute code, but they also read files, write files, install packages, cache intermediate state, and shell out to other processes as part of ordinary tool use. That means the filesystem, not the model, is where most real failures actually happen: an unauthorized read of a credentials file, a write that crosses from one sandbox into another, a cached secret that survives past the session that created it. In July 2025, an AI coding agent deleted a production database nine days into a twelve-day experiment run by SaaStr founder Jason Lemkin; whatever the model's reasoning was leading up to that point, the failure that mattered was a runtime boundary that let a destructive command reach a database it should never have touched. Around the same period, security researchers testing all sixteen public AI agents from a YCombinator batch found seven compromised, with outcomes ranging from leaked user data to remote code execution to, in one case, a fully deleted database. Layer onto that a separate, uncomfortable finding: LLM-generated code patches introduce new security vulnerabilities in roughly 9.5% of cases, even when the patch successfully fixes the bug it was written for. So the code hitting the sandbox is already less trustworthy, on average, than code a person wrote and reviewed, and the filesystem is where that gap between "looks fixed" and "is safe" turns into an actual incident. No single clever mechanism solves this; the outcome depends on how several imperfect mechanisms stack.
What filesystem isolation actually means in a sandbox context
Filesystem isolation, stated plainly, means stopping a sandboxed process from reading, writing, or even seeing any part of the host filesystem, or another sandbox's filesystem, that it wasn't explicitly given. That's distinct from process isolation (which governs PIDs, signals, who can kill or inspect whom) and network isolation (which governs what the sandbox can reach over the wire), though the three interact constantly. A filesystem leak often becomes a process leak; a process leak often becomes a privilege escalation that reopens the filesystem question from a worse position.
A handful of primitives do the actual work. Mount namespaces give each process tree its own private view of the filesystem hierarchy, which is the foundation everything else is built on top of. Chroot, and its more modern cousin pivot_root, restrict which directory a process treats as its root; it's the oldest trick in this toolbox, and, as the next section gets into, one of the most widely misunderstood. Overlay filesystems let a sandbox share a read-only base image with hundreds of other instances while keeping its own writable layer on top, avoiding the cost of copying an entire OS image per sandbox. Bind mounts and virtual mounts like tmpfs, procfs, and devtmpfs selectively expose or hide specific paths, giving a sandbox just enough access to shared libraries or kernel interfaces to function without handing over the whole host. And underneath all of that sits a permission layer, DAC, MAC, seccomp, that governs what a process can actually do within whatever scope it's been given.
It helps to keep three separate questions in mind, because they get conflated constantly in casual conversation about "isolation." What can the process see? What can it change? And do those changes survive after the session ends? Visibility, writability, and persistence are three different axes, and no single technique on the list above answers all three at once. That's the whole reason isolation ends up layered rather than solved by one mechanism: chroot mostly answers visibility, overlays mostly answer writability and persistence, and permission layers constrain what a process can do even inside the parts it's allowed to touch.
How chroot and mount namespaces form the base layer — and where they stop
Chroot has been around for decades, making it one of the oldest tools in this space. Its job is simple: change what a process, and its children, treat as the root directory. Everything above that point becomes invisible to the process, at least in theory.
The "in theory" is doing real work in that sentence. Chroot doesn't touch a process's kernel-level capabilities. A process running as root inside a chroot jail can, with a small number of syscalls (open, chdir, fchdir, used in the right sequence), break back out of it. Chroot also has nothing to say about network namespaces, PID namespaces, or IPC, so a chrooted process, absent other controls, can still see and send signals to processes running well outside its jail. And every shared library or device file the sandboxed process needs has to be manually duplicated or bind-mounted into the jail, which means every one of those bind mounts is a fresh opportunity to misconfigure something.
Mount namespaces, introduced in an early Linux kernel release, close the specific gap chroot leaves open around visibility. Each namespace gets its own independent mount table, so anything mounted inside the sandbox stays inside the sandbox; it doesn't propagate back out to the host, and the host's mounts don't automatically show up inside. Combine chroot with a mount namespace and a process gets a genuinely private, contained view of the filesystem.
But both mechanisms still run on the host's kernel. That's the ceiling. A kernel vulnerability, or a misconfigured syscall path, can let a process escape a properly configured namespace-plus-chroot setup entirely, because the isolation is a construct the kernel maintains, not a hard wall the kernel itself sits behind. CVE-2024-21626 showed exactly this: a container escape that let a process step outside its boundary despite namespaces being in place. Practically, that means chroot and mount namespaces are a reasonable component of a layered sandbox for code that's semi-trusted. Neither one, alone or combined, is enough for code an AI agent generated and nobody reviewed.
Overlay filesystems and copy-on-write layers — performance wins and hidden risks
OverlayFS solves a real, expensive problem: how do you give a thousand sandbox instances a full operating system and language runtime without copying that entire image a thousand times? The answer is to split the filesystem into layers. A read-only lower layer holds the base image, the OS, the runtime, whatever tools come preinstalled. A per-sandbox upper layer holds only the files that particular sandbox has actually changed, copy-on-write, so nothing gets duplicated until it's modified. The process sees a merged view that looks, from the inside, like one ordinary filesystem.
The same pattern shows up in microVM environments through device mapper, which is how Firecracker VMs get shared base images with individual writable overlays, essentially the same trick Docker uses for its own image layers. The performance payoff is not subtle: with pre-warmed snapshots and copy-on-write memory overlays, end-to-end sandbox provisioning can land around 28 milliseconds, which is faster than a lot of database round trips.
That efficiency comes with a specific set of risks attached. The lower layer is shared across every sandbox using that base image, so if an attacker manages to write to it, through a runtime vulnerability, a misconfigured mount, or a host kernel escape, every sandbox built on that image is compromised at once. The upper layer, meanwhile, is supposed to be thrown away at teardown; any leak path, a persistent volume left mounted, a bind mount that wasn't cleaned up, lets one session's data bleed into the next. Sometimes that's intentional; letting an agent resume mid-task by keeping its writable layer around is a legitimate feature. Left unmanaged, though, it's a straightforward vulnerability. OverlayFS also carries a history of edge-case bugs around file deletion (the whiteout mechanism it uses to represent "this file was removed"), extended attributes, and rename operations, any of which can leak metadata across a layer boundary that's supposed to be sealed.
So the statefulness question is a design decision someone has to make deliberately. For an AI agent that needs to pause and resume a long task, the upper layer is precisely what should get snapshotted and restored. Platforms like Daytona, a cloud sandbox infrastructure for executing AI-generated code, treat that snapshot lifecycle as a first-class concern. But that requires a runtime that manages the snapshot lifecycle on purpose, not one that just happens to leave the layer sitting there between sessions. Copy-on-write overlays are close to essential at production scale; whether they're safe comes down to whether someone decided how they behave at teardown, or left it to chance.
Virtual mounts and controlled path exposure — what the sandbox is allowed to see
Even a well-built overlay needs some access to the outside world to actually run anything: shared libraries, device nodes, a handful of kernel interfaces. That access comes through virtual mounts, and each type carries its own particular risk if it's exposed too generously.
Tmpfs is an in-memory filesystem, safe for scratch space since nothing written there survives teardown, and it's commonly used for /tmp specifically so a runaway sandbox process can't fill up the host's actual disk. Procfs, mounted at /proc, exposes kernel and process metadata; left unrestricted, a sandbox can use it to read other processes' memory maps, open file descriptors, and environment variables, so production setups need it filtered or remounted with a narrower, curated view. Devtmpfs, at /dev, exposes device nodes, and an unfiltered /dev inside a container is one of the oldest, most reliable escape vectors around; the fix is a minimal /dev populated only with the specific devices that workload actually needs, nothing more. Sysfs, at /sys, exposes hardware and kernel configuration data, and it's usually mounted read-only or hidden outright in any sandbox meant to run untrusted code.
Bind mounts deserve their own mention, since they're a deliberate choice to expose one specific host path inside the sandbox, say, a shared directory of build tools. Useful, but every bind mount is a potential leak or escape route if the directory it points to has broader permissions than the sandbox actually needs. The anti-pattern that shows up over and over: a directory gets bind-mounted in that happens to contain cloud credentials, SSH keys, or API tokens, and an agent that can read the filesystem will, eventually, find them and potentially exfiltrate them. That's a description of what a filesystem-reading agent does by default when nobody has fenced off what it's allowed to see, regardless of whether any malicious intent is involved.
Production practice, distilled: minimal /dev, read-only /proc with a filtered set of entries, no bind mounts that carry secrets, tmpfs for anything writable and disposable. Each of those is a real reduction in attack surface, but each one is also a configuration decision someone has to make correctly. Most container runtimes don't ship with defaults hardened for untrusted, AI-generated code; they ship with defaults tuned for convenience, and the burden of tightening them falls on whoever's building the sandbox.
Seccomp, capabilities, and MAC policies as the permission boundary underneath the filesystem
A process can sit inside a mount namespace with a nicely configured copy-on-write overlay and still make arbitrary syscalls straight to the host kernel. Some of those syscalls, open_by_handle_at, unshare, ptrace, mknod, are exactly the tools an escape or privilege escalation would use. This is the layer that decides whether the filesystem boundaries above actually hold.
Seccomp-BPF filters syscalls before they reach the kernel at all: every syscall a sandboxed process attempts gets checked against a BPF program, and depending on how the filter's written, a disallowed call can return an error (commonly EPERM), trigger SIGSYS, or kill the process outright. Docker's default seccomp profile blocks a subset of available syscalls, which is a sane starting point but not a hardened one for AI-generated code specifically. A production sandbox built for that workload should go further: block unshare to prevent namespace escapes, block ptrace to stop one process from inspecting another's memory, block mknod to prevent new device nodes from being created inside the sandbox at all.
Capabilities matter alongside seccomp because "root" inside a container carries a narrower set of privileges than root on the host, but it still carries a set of capabilities that need actively dropping. CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_DAC_OVERRIDE, CAP_NET_ADMIN, each of these left in place gives a sandboxed process room to reconfigure its own mounts or reach host devices it has no business touching. Mandatory access control systems, AppArmor and SELinux being the two most common, add a policy layer that applies even to root, restricting which paths are reachable regardless of what the standard Unix permission bits say.
gVisor takes a structurally different approach to this whole problem. Rather than filtering syscalls on their way to a shared kernel, gVisor intercepts them in a user-space process called Sentry; the sandboxed workload never talks to the host kernel directly at all. That shrinks the syscall surface that actually needs defending down to whatever Sentry itself requires, rather than the full range any arbitrary workload might attempt. It's not free: I/O-heavy workloads see something like 10 to 30% overhead under gVisor, which matters a lot for an agent doing constant file reads and writes. Firecracker sidesteps the syscall-filtering question in a different way, by giving each workload its own dedicated kernel; even a full kernel compromise stays contained to that one microVM. It boots in around 125 milliseconds with under 5 MiB of overhead per VM, which is a genuinely different tradeoff than either the container or gVisor model offers.
The throughline across seccomp, capabilities, and MAC policies: this layer closes the gaps every technique above it leaves open, rather than functioning as optional hardening on top of some separate, more fundamental isolation. Mount namespaces and overlays define what a process can see and where its writes land; this layer decides whether the process can abuse what it's allowed to see in the first place.
How isolation strategies compose across the four main sandbox architectures
None of the mechanisms above run alone in a working system. A real sandbox is a specific combination of them, and that combination is what actually determines the tradeoff between security, speed, and statefulness, not any single technique in isolation.
Hardened containers, meaning Docker plus seccomp plus AppArmor or SELinux plus a stripped capability set, lean on mount namespaces, OverlayFS, a minimal /dev, and syscall filtering all at once. The strength here is speed: fastest provisioning of the group, smallest overhead, and full compatibility with the existing Docker ecosystem. The gap is the shared host kernel; a kernel exploit or a misconfiguration, exactly what CVE-2024-21626 demonstrated through runc, can step past the namespace boundary regardless of how carefully everything else was configured. That makes hardened containers a reasonable fit where speed and compatibility matter most and the code running isn't fully untrusted.
gVisor-isolated containers keep the mount namespace and OverlayFS layer but replace direct kernel syscalls with Sentry's user-space interception, so the host kernel never sees most filesystem operations directly. That cuts the kernel attack surface dramatically without paying for a full virtual machine, and GPU support added in 2024 and 2025 makes it a workable option for AI inference workloads specifically. The tradeoff is that 10-to-30% I/O overhead mentioned earlier, plus the reality that Sentry hasn't implemented every syscall or kernel feature that a complex workload might need, so compatibility gaps do come up. This fits multi-tenant AI workloads where a kernel escape is unacceptable but a full VM's overhead isn't practical either.
Firecracker microVMs push isolation down to the hardware level: each VM gets its own kernel and its own filesystem namespace from the moment it boots, so there's no shared kernel surface to escape into at all, while device-mapper-based copy-on-write snapshots still provide per-VM writable overlays over a shared base image. An attacker has to escape both the guest kernel and the hypervisor to reach anything else, which is a meaningfully higher bar. Boot time lands around 125 milliseconds, and a single host can support up to 150 VM launches per second. The catch is GPU passthrough isn't built in, which rules Firecracker out for workloads that need per-session GPU access, and filesystem persistence across sessions still needs explicit snapshot management rather than coming for free. This is the fit for high-security execution of fully untrusted AI-generated code where GPU access isn't part of the requirement.
Kata Containers occupy a related space: a lightweight virtual machine wrapped around each container, staying OCI-compatible while getting hardware-enforced kernel separation similar in spirit to what Firecracker provides, aimed at teams that want VM-grade isolation without leaving the standard container tooling and workflows behind.
Looked at together, the four architectures represent four different points on the same set of tradeoffs: speed against kernel exposure against GPU access against how much statefulness the runtime is willing to manage explicitly. The question worth asking about any sandbox claiming to be secure is which combination of namespaces, overlays, and seccomp it uses, and which of the gaps described in each section above that combination still leaves open.


