Read-Only Mounts and Tmpfs Volumes for Sandbox Filesystem Hardening
Read-only mounts and tmpfs volumes prevent agents from persisting malicious code changes.

Read-only mounts and tmpfs volumes harden an agent sandbox by separating files the agent may read from places it may write. Combined with mount flags such as noexec and nosuid, they put limits on filesystem changes that do not depend on the agent following instructions. The central principle is to grant only the writable space a task needs, while treating execution, persistence, and access to secrets as separate permissions.
Why AI-generated code needs filesystem-level containment, not just process isolation
Process isolation does not stop an agent from damaging files it is allowed to change inside its container. An agent with broad write access can plant malicious binaries, overwrite interpreter scripts, or corrupt shared state without escaping that boundary. The risk follows from how agents work, generating and executing novel code from inputs that may be attacker-controlled. Unlike traditional application code whose behavior is fixed at compile or deploy time, those generated actions cannot all be audited before execution. Filesystem containment therefore has a distinct job, which is to limit what an agent can change after its judgment has been compromised.
CVE-2025-58372 in Roo Code illustrates how a workspace write can become arbitrary code execution following prompt injection. The filesystem can hold the hostile input that leads the agent to make a malicious change to files there. A process boundary alone leaves that path open if the files within it remain broadly writable.
What a read-only root filesystem actually prevents at the kernel level
A read-only root filesystem makes the base environment unavailable for modification. The kernel rejects writes to that mount, preventing the agent from replacing binaries, overwriting system libraries, or installing backdoors there. Docker exposes this control through --read-only on docker run, which sets ReadonlyRootfs to true in the container specification. This is an enforcement rule below the agent. Its scope is the read-only mount, so separately mounted writable paths still need their own policy.
The timothy-agent PR (#750) identified the writable container layer as a remaining gap and added ReadonlyRootfs set to true. That before-and-after distinction matters because restricting a process does not automatically make its filesystem immutable. Making the root read-only removes that writable base. It does not, however, remove the need to decide which files the agent should see.
Read-only access protects integrity. An agent can still read visible secrets, including ~/.aws/credentials, .env files, and SSH keys, and the root-mount setting does not stop network exfiltration. Files that are not mounted into the container remain outside that view, which makes mount selection part of the security boundary. When the configuration is kept in the repository, team members have the same declared environment.
Where writable tmpfs volumes fit into the read-only picture
A read-only root becomes practical only when the agent has designated places to write. Tmpfs supplies RAM-backed scratch space that disappears when the container exits. The hardened pattern combines an immutable base, explicit writable mounts for authorized working directories, and size-capped tmpfs for temporary work. The size cap limits the memory a runaway agent can consume through that scratch space.
A concrete layout separates paths by purpose. Scratch directories such as /tmp and /dev/shm belong on tmpfs, while /app or /workspace can be explicit bind mounts containing only the project the agent is authorized to change. Runtime and cache directories also need writable space, but that space should stay within the agent’s own runtime prefix. These choices distinguish disposable intermediate files from project changes that legitimately outlive a run. These deployment choices are separate from arXiv:2512.14860, which concerns comparative penetration testing.
The boundary must also separate one sandbox’s scratch space from another’s. Agents sharing a host-level /tmp can create conflicts and expose information across sandbox instances, so each sandbox needs isolated tmpfs. Isolation between tenants does not settle which files are safe to mount within a tenant’s sandbox, however. Claude Code’s secure deployment documentation says that even read-only access to a code directory can expose credentials.
What noexec and nosuid add on top of a tmpfs mount
Scoping tmpfs limits where writes land, but it does not by itself restrict how those files are used. The noexec flag prevents direct execution of files on the mount and executable mappings through mmap(PROT_EXEC). The nosuid flag prevents the mount’s setuid and setgid bits from granting elevated privileges when files are executed. The nodev flag prevents device files on the mount from being interpreted as devices.
The combined effect is narrower than a general ban on harmful computation. An agent can write a binary into scratch space, but that mount cannot serve as the place from which the binary is directly executed. Nor can the agent rely on a setuid file there to acquire elevated privileges. These restrictions complement the read-only root by limiting what writable exceptions can become. They do not revoke execution permissions or readable data elsewhere in the sandbox.
OpenAI Codex makes related access choices explicit through its read-only, workspace-write, and danger-full-access modes. Its default sandbox mode is workspace-write, with network_access = false by default in that mode. This separation matters because writable workspace access and network access answer different security questions. Mount flags are similarly specific controls within the sandbox policy.
The noexec debate: where the flag genuinely breaks legitimate agent workflows
The strongest objection to noexec is that scratch space often participates in legitimate execution. Agents may need to run npm install scripts, pip post-install hooks, and shell scripts that land in or stage through temporary directories. A blanket prohibition can therefore break ordinary build and installation work. The resulting failure may show that the storage policy and the toolchain expect different things from the same path.
The timothy-agent PR (#750) documented a deliberate decision not to set noexec on agent tmpfs. The team argued that it stopped valid scripts from running and added no protection beyond what their container boundary already gave them. Their setup had already dropped all Linux capabilities and blocked privilege escalation at the container level. A subsequent PR (#997) found that Docker was silently applying noexec anyway and changed the flag configuration to restore execution.
The choice turns on the threat model. An agent that handles untrusted external inputs in a shared-kernel container poses a different risk from a workflow that relies more on the isolation controls it already has. For the former, restricting execution from scratch space provides an additional barrier even though it cannot make the container boundary complete. The practical pattern is to apply noexec to /tmp by default and provide a separate, explicitly scoped writable-and-executable mount where build tooling genuinely requires it.
How filesystem hardening fits inside the broader isolation stack
Filesystem hardening limits tampering and persistence, but a compromised agent need not write anything to cause harm. It can read a mounted secret and send it over the network using access it already holds. Network egress controls limit what the agent can send over that path, while choosing mounts with care limits the data it can steal.
The underlying isolation primitive changes what those controls are supporting. Standard containers share the host kernel, and gVisor documentation describes a container workload as “only one system call away from host compromise”. gVisor intercepts syscalls in a user-space kernel before they reach the host kernel, giving mount restrictions another enforcement boundary to complement. Firecracker microVMs provide a dedicated kernel per workload and the strongest base isolation among these options. Filesystem hardening still matters there because preventing changes inside a sandbox is a different goal from preventing escape.
These layers should be judged by the failure each contains. A stronger kernel boundary still leaves the choice of which project directory an agent may rewrite. Likewise, a read-only root does not make a shared kernel equivalent to a dedicated one. Judging each control by what it protects keeps one from getting credit for the work of another. It also leaves room for authorized persistence without making every part of the running environment mutable.
Stateful agent workflows and where filesystem state legitimately needs to persist
Ephemeral scratch space is useful precisely because it disappears, but long-running agents cannot discard everything after every session. Dependencies installed during one run, caches built across tasks, and earlier analysis may all help with later work. Rebuilding that context each time defeats part of the purpose of a high-context coding agent. The design must therefore preserve useful state without turning the live filesystem into unrestricted durable storage. Stateful and hardened are not opposing properties.
Tmpfs can hold context for the current task, including retrieved documents, intermediate reasoning, and tool call results that should be cleared when the task ends. State intended to survive belongs in an explicitly scoped, auditable store outside that disposable space. This distinction gives persistence an intentional boundary. A writable path should not be kept across runs just because something useful happened to land there.
Snapshots extend the design by capturing filesystem and memory state so a running sandbox can resume from an exact point without rebuilding its environment. In that model, the snapshot supplies persistence while the live sandbox remains ephemeral. The distinction between disposable context and retained state still matters, because a memory snapshot is itself a way to preserve state. Snapshot-based persistence fits agents that accumulate dependencies and analysis, but it does not justify broadening their runtime write permissions. The snapshot captures authorized state.
Applying the pattern in a purpose-built agent sandbox versus a retrofitted container
A generic container can express this hardening pattern, but its operator must configure, maintain, and audit the pieces. Docker does not enable --read-only by default, and a separate writable mount can reopen a path the root setting was intended to protect. A missing nosuid, uncapped tmpfs, or shared scratch directory leaves a different gap. Tooling adds operational friction because required runtimes and binaries must be pre-baked into the image or installed through an explicitly allowed runtime path. Ad-hoc installation into the base environment is exactly what a read-only root forbids.
Even when mounts are set up correctly, weaknesses in the isolation primitive remain. CVE-2019-5736 and CVE-2024-21626 involved runc flaws that enabled container escape through the runtime. Per-sandbox tmpfs addresses cross-tenant scratch exposure, while runtime isolation addresses a different class of failure.
A purpose-built agent sandbox can make these policies part of its runtime contract rather than leave them as conventions for each operator to reconstruct. That means isolated tmpfs by default and enforced filesystem restrictions, with Docker-native compatibility allowing existing images to carry over. The benefit is not the label attached to the platform, but which configuration gaps its design closes. An open-source runtime also lets operators see how mount policy is applied, so they need not rely on a black box. The practical standard remains the same in either setting, which is a read-only base, narrow writable exceptions, explicit execution permissions, and deliberate persistence.


