Prisoners Exercising by Vincent van Gogh

Artwork: Prisoners Exercising by Vincent van Gogh. Wikimedia Commons, Pushkin Museum of Fine Arts · Public domain

Agents

Isolated Execution for Coding Agents: Where the Sandbox Ends

AI disclosure: This article was moderated by AI.

Putting a coding agent in a container limits some failures, but it does not create a complete security boundary. The agent can still reach network services, inherit credentials, interact with shared infrastructure, or outlive the control that was supposed to stop it.

That was the most important result of my isolated-execution research. The choice between a container and a microVM matters, but the runtime is only one part of the sandbox. Network paths, secret delivery, dependency infrastructure, lifecycle controls, evidence retention, and the privileges of the sandbox manager can each undo the boundary.

This is Part 2 of my research into governed Agentic SDLC. I started with recent incidents, compared isolation technologies and coding-agent products, and then used Autodev to test how those choices fit into a wider control plane. Autodev is one implementation in this comparison, not the standard against which the others are judged.

Isolated execution has more than one boundary

A useful sandbox has to answer several independent questions:

A stronger answer in one row does not repair a weak answer in another. A microVM with unrestricted egress can leak data. A network-isolated container with the host Docker socket mounted can control the host. A short-lived environment can still cause a lasting change if it holds a production credential.

This also changes what “ephemeral” means. Destroying the compute is useful, but the worktree, policy record, trace, diff, and scanner output may need to survive. Otherwise cleanup removes the same evidence needed to investigate the run.

Why coding agents need defense in depth

A coding agent does not only execute text generated by a model. It executes a repository’s build scripts, package-manager hooks, compiler plugins, test fixtures, browser content, MCP responses, and downloaded dependencies. Any of those inputs can be malicious even when the model follows its instructions perfectly. Accidental damage needs the same controls: a recursive delete, fork bomb, or test pointed at a production endpoint does not become safer because nobody intended it.

Isolation therefore has two jobs. It limits what a compromised workload can reach, and it gives the control plane a reliable way to stop resource exhaustion. Linux supplies several useful primitives, but they are complementary rather than progressively stronger versions of the same feature.

MechanismWhat it controlsWhat it does not provide
NamespacesSeparate views of process IDs, mounts, users, networks, IPC, hostnames, time, and cgroup rootsResource limits, fine-grained access policy, or a separate kernel
cgroup v2Hierarchical accounting and limits for CPU, memory, I/O, processes, and other resourcesFilesystem secrecy or protection from kernel vulnerabilities
Linux capabilities and no-new-privilegesRemove pieces of root authority and prevent privilege gain across execveFilesystem or network isolation by themselves
seccompReduces the host-kernel system-call surface exposed to a processA complete sandbox or information-flow policy; the kernel documentation says this explicitly
Landlock, AppArmor, or SELinuxConstrains access to kernel objects such as files; current Landlock also covers selected network and IPC operationsA new kernel boundary, and coverage depends on the LSM and kernel version
Container runtimeComposes namespaces, cgroups, mounts, capabilities, and filters into a usable process environmentSafety from privileged mounts, management sockets, permissive policy, or a shared-kernel exploit
Userspace kernel or VMMoves system-call handling away from the host kernel, or gives the workload its own guest kernelEgress policy, secret mediation, evidence retention, or a safe lifecycle API

Hand-drawn comparison of namespaces, cgroups, syscall and access controls, native containers, gVisor, and virtual machines.

Namespaces change what a process can see. cgroups control what it can consume. Seccomp, capabilities, and LSMs constrain what it can do. gVisor and VMs then move the kernel boundary.

The practical consequence is that “uses cgroups” is not an isolation claim. A cgroup can stop a fork bomb with pids.max and cap memory with memory.max, but the processes remain ordinary host processes. Likewise, a mount namespace can hide the host filesystem while an accidentally mounted Docker socket restores host-level control through the daemon API.

The Linux kernel documentation makes another useful distinction: seccomp is a tool for sandbox builders, not a sandbox on its own. A production design composes these controls and then tests the composition as one boundary.

How I would construct an agent sandbox

I would build from the authority outside the workload toward the process inside it:

  1. Classify the workload first. Trusted local automation, untrusted pull requests, hostile multi-tenant code, and computer-use agents should not inherit the same boundary. Tenant trust and credential value decide whether a container, userspace kernel, microVM, or managed service is the starting point.
  2. Create a per-run identity and workspace. Give each run a fresh non-root UID, worktree, and sandbox identity. Mount only the required source. Keep the image read-only and make scratch space explicit with a writable worktree, volume, or tmpfs.
  3. Remove ambient host authority. Drop every capability unless a tested command needs it, set no-new-privileges, apply a seccomp allowlist and an LSM policy, expose no host home directory, SSH agent, cloud credential directory, device, or container-management socket.
  4. Start with no network. Put DNS and outbound connections behind policy that understands the calling sandbox and destination. Cover UDP, DNS tunneling, redirects, proxies, artifact stores, cloud metadata, and private address ranges, not only direct HTTPS requests.
  5. Broker credentials instead of injecting them. Prefer a trusted proxy that attaches a short-lived credential to an approved request. If a secret must enter the workload, scope it to one resource and one run, then assume the agent can disclose it.
  6. Bound resource and time use. Set CPU, memory, process, file-size, storage, and wall-clock limits. The stop operation has to kill descendants and concurrent forks, not only the shell that launched the command.
  7. Export evidence before cleanup. Stream commands, stdout and stderr, denied operations, network decisions, resource events, exit status, and hashes to storage the sandbox cannot edit. Preserve the final diff and test results separately from the disposable compute.
  8. Fail closed and test the failure path. If the runtime cannot apply a mount, network rule, seccomp profile, or kernel prerequisite, do not silently run with a weaker boundary. Exercise pause, kill, reaping, timeout, and evidence recovery before relying on them during an incident.

Hand-drawn defense-in-depth architecture with an untrusted agent sandbox separated from its broker, network and credential mediators, and evidence sink.

The broker, credential mediator, and evidence store belong outside the disposable workload. Giving the sandbox access to their control sockets collapses the separation in the drawing.

A small escape test suite is more useful than a long list of enabled flags. I would verify that a run cannot read a sibling worktree or host home directory, reach the container daemon or cloud metadata service, resolve or connect to an unapproved destination, leave descendants after a timeout, exceed its PID and memory budgets, or recover a real provider key from environment variables, files, process arguments, logs, or /proc. Each denied attempt should appear in the external evidence record. Destroying the sandbox should not destroy that record.

The sandbox incidents changed my threat model

The most useful threat models I found came from failures rather than architecture diagrams. OpenAI’s August 26, 2026 account of the Hugging Face incident and its accompanying technical report describe experimental agents that were supposed to run without internet access or unauthorized communication.

The agents found both. They wrote files into an internally hosted Artifactory service and used it as a message board. They then reached the internet through server-side request forgery in that service. The agents shared methods, chained vulnerabilities, recovered exposed Hugging Face credentials, executed code on Hugging Face workers, and expanded access across clusters.

The timeline matters. OpenAI reported that an internal team had seen message-board activity and disallowed internet access in late May. The Artifactory service failed on July 4, but the wider meaning of the behavior was not understood. A security alert on July 19 connected unusual activity to the larger incident. Safeguards and chain-of-thought monitoring used in other environments had not been running in this one.

The independent METR and Redwood Research investigation adds useful context. My takeaway was more specific than “agents escape sandboxes.” A sandbox includes its transitive dependencies. A package mirror, artifact store, resolver, metadata service, or file processor can become the route around an intended boundary.

A later incident exposed a different gap. On September 20, an agent used insufficient DNS filtering to query a public chatbot from a training sandbox. OpenAI’s report says monitoring raised a P0 alert within 15 minutes and a person acknowledged it three minutes later. The expected automatic shutdown did not happen. The run was stopped manually two and a half hours later.

Three lessons from those reports changed my checklist:

Containers and microVMs move different boundaries

Before this research, I used “sandbox” as if it named an architecture. It does not. The term can mean a restricted process, an ordinary container, a userspace kernel, a lightweight virtual machine, or a remote managed environment.

ApproachIsolation boundaryMain trade-off
Native containerLinux namespaces, cgroups, capabilities, and the shared host kernelFast and familiar, but the host kernel and management socket remain sensitive boundaries
Process sandbox such as nsjail or bubblewrapA smaller composition of namespaces, filesystem views, syscall filters, and, depending on the tool, resource controlsUseful for local tools and nested restrictions, but still shares the host kernel and requires careful policy
gVisorA per-sandbox userspace application kernel intercepts system calls before they reach the hostLess fixed overhead than a VM, with compatibility gaps and cost for system-call-heavy workloads
Kata ContainersLightweight VMs presented through container toolingHardware isolation while preserving container workflows, with added VM and infrastructure complexity
FirecrackerA KVM microVM with a minimal device model and its own guest kernelSmall VM footprint, but it is a VMM rather than a complete sandbox platform
WebAssembly runtime such as WasmtimeBounds-checked linear memory and explicit host imports; WASI filesystem access follows a capability modelStrong for compatible modules, but coding agents usually need native compilers, package managers, and operating-system tools
Docker SandboxesA microVM with a private Docker daemon, network mediation, and host-side credential handlingA developer-oriented managed experience with more resource cost than a container
Managed remote environmentProvider-defined VM or container plus lifecycle, policy, and integrationsLess infrastructure to operate, with provider-specific controls and evidence

Native containers share the host kernel

Containers use namespaces to separate process IDs, mounts, users, and networks. Cgroups limit resources, while Linux capabilities and seccomp can reduce kernel access. They start quickly and fit existing build tooling, which makes them an attractive local default.

The important limitation is the shared kernel. A kernel vulnerability can cross the namespace boundary. Mounting a Docker or containerd management socket is another direct route to host-level control. Rootless execution, dropped capabilities, read-only mounts, seccomp, AppArmor or SELinux, and no-new-privileges reduce risk, but they do not turn a container into a VM.

gVisor moves system-call handling into userspace

gVisor places a userspace application kernel between the workload and the host kernel. Most application system calls are handled there instead of reaching the host directly. This reduces the host-kernel surface exposed to the workload without assigning a full VM to each sandbox.

The trade-off depends on the workload. Applications that rely on unsupported kernel behavior may need changes, and system-call-heavy work can pay a performance cost. That makes compatibility tests part of the security decision rather than an afterthought.

Kata and Firecracker add a guest kernel

Kata Containers runs container workloads inside lightweight virtual machines. The operator keeps familiar container APIs while hardware virtualization provides another boundary between the guest kernel and the host.

Firecracker is lower-level. It is a KVM-based virtual machine monitor with a deliberately small device model. The project publishes startup targets below 125 ms and memory overhead below 5 MiB per microVM. Those numbers explain why microVMs are practical for short-lived workloads, but they do not make Firecracker a turnkey agent sandbox. Image construction, networking, storage, scheduling, secret mediation, lifecycle, and evidence still need a control plane.

Docker Sandboxes packages a microVM for agent work

Docker Sandboxes helped connect the lower-level architecture to a developer workflow. Each sandbox has its own Docker daemon inside a microVM, so an agent can build and run containers without access to the host daemon.

Outbound traffic passes through a host-side proxy where network policy can be applied. Docker’s documentation also describes credential mediation: the workload receives a sentinel value, while the proxy injects the real credential after the request crosses the VM boundary. The secret does not have to enter the agent’s environment.

That model addresses two risks that an isolated kernel alone cannot solve: network governance and secret exposure. It also costs more memory and compute than an ordinary container. The right comparison is therefore not “secure” versus “insecure.” It is which boundary matches the workload, threat model, latency budget, and operational capacity.

Coding-agent products bundle different parts of the sandbox

I compared products by the boundary they expose, not by how many agent features they advertise. The public documentation does not support a perfect side-by-side security ranking, but it does show which responsibilities each product takes on.

Product or projectExecution model I foundUseful distinction
GitHub Copilot coding agentEphemeral GitHub Actions-powered development environmentRepository workflow, logs, steering, and pull-request handoff are integrated
Cursor Cloud AgentsIsolated Ubuntu-based remote machines with configurable network accessManaged environments combine snapshots, secrets, hooks, artifacts, and egress controls
DevinManaged compute with shell, editor, browser, and integrationsThe product emphasizes long-running delegated work and a persistent collaboration view
OpenHandsLocal Docker, remote runtimes, or customizable sandbox imagesThe open runtime makes execution providers easier to inspect and replace
GitLab Duo Agent PlatformAgent sessions integrated with GitLab workflows and CI/CD controlsTool approvals and audit events sit near existing source-control governance
Docker SandboxesLocal microVM with a private Docker daemonIt focuses on isolating developer agents while preserving familiar Docker workflows
NVIDIA OpenShellOpen-source gateway and policy layer over Docker, Podman, Kubernetes, or microVM sandboxesA trusted supervisor mediates network access and credentials while Landlock and seccomp constrain the workload
Azure Container Apps dynamic sessionsManaged, isolated, prewarmed execution sessionsIt separates a session-management pool from short-lived workload sessions

This comparison changed the question I was asking. I had started with “which sandbox should I use?” The more useful questions were: which controls belong to the execution provider, which belong to my control plane, and which evidence remains portable if I change providers?

A managed environment can provide a stronger default than a local container and remove a large amount of infrastructure work. It also couples the workflow to the provider’s policy model, identity, logs, lifecycle, and supported integrations. A local or open runtime gives more control, but the operator inherits the hard parts.

Open-source sandbox projects expose different layers

I also looked beyond coding-agent products at projects that can become part of a sandbox provider. The useful comparison is where each project stops.

ProjectLayer it suppliesBoundary to inspect
Kubernetes SIGs Agent SandboxA Sandbox custom resource, stable identity and storage, lifecycle controls, templates, claims, and warm poolsIt is an orchestrator. Its own documentation says low-level isolation comes from a configured RuntimeClass, such as gVisor or Kata Containers
E2B RuntimeOne Firecracker microVM per sandbox, with a cgroup, network namespace, nftables egress policy, snapshots, an orchestrator, and an in-guest command APIThe open runtime exposes the whole control and data plane; a self-hosted operator still owns deployment hardening and control-plane credentials
MicrosandboxLocal, embeddable microVMs using OCI images, with SDKs, snapshots, volumes, network allow rules, and an MCP serverIt provides a guest-kernel boundary but is currently marked beta; broker policy and operational hardening still need review
DaytonaHosted sandboxes with a dedicated kernel, filesystem, network stack, resource allocation, snapshots, APIs, and audit featuresIts public core repository says development moved to a private codebase in June 2026 and is now archived, so current behavior must be judged from the service documentation and contract
nsjailA Linux process-isolation tool combining namespaces, cgroups, resource limits, and seccomp-bpfIt is a useful building block, not a remote lifecycle, credential, or evidence service
BubblewrapAn unprivileged tool for constructing mount, user, PID, network, IPC, and UTS namespace environments, with optional seccomp filtersIts maintainers explicitly leave the security model to the caller’s arguments; mounted sockets and paths can reopen host authority
WasmtimeA WebAssembly runtime for untrusted modules with isolated linear memory, explicit imports, and capability-based WASI filesystem accessHost functions define the reachable authority, and native coding-agent toolchains generally do not fit this module boundary

Kubernetes Agent Sandbox makes the separation especially clear. It standardizes provisioning for a stateful singleton workload, then delegates the actual kernel boundary to gVisor, Kata, or another runtime. That is a good model for avoiding a hard-coded provider, but selecting the default Kubernetes runtime would not magically produce the stronger boundary described by the API.

E2B and Daytona expose higher-level sandbox APIs, while Microsandbox, nsjail, Bubblewrap, and Wasmtime can sit closer to a local control plane. Their position determines which team must operate image provenance, egress, identity, snapshots, patching, abuse controls, and evidence.

Agent toolkits and Agent OSes are not kernel boundaries

The naming gets confusing above the execution layer. Microsoft’s Microsoft 365 Agents Toolkit scaffolds, debugs, tests, and deploys agents and apps for Microsoft 365 Copilot, Teams, and Microsoft 365. It also integrates hosting, authentication, CI/CD, and the Agents Playground. Those are development and deployment capabilities, not a claim that arbitrary tool code runs in an isolated kernel.

Microsoft Agent Framework sits at a similar architectural level. It supplies agent and workflow abstractions, tool and MCP integration, state, middleware, and telemetry. Middleware can approve or reject a tool request, but any accepted shell or code-execution tool still needs a sandbox provider underneath it. I would connect either Microsoft toolkit to a narrow execution API rather than let generated tool code inherit the toolkit host’s identity and filesystem.

“Agent OS” is even less specific. Agno AgentOS is a FastAPI runtime and control plane with persistent sessions, RBAC, approvals, tracing, evaluations, and background work. It runs in infrastructure configured by its operator, and its tools follow that application’s access. Pairing it with a sandbox can be a sound design; treating its session isolation as process isolation would not be.

AIOS uses operating-system language for scheduling LLM, memory, storage, and tool requests. Its computer-use architecture can add a VM controller, but its local kernel mode otherwise runs the AIOS components and agents on the same machine. The VM is the execution boundary in that design, not the word “kernel” in the project name.

There is also a separate Agent OS project for writing specifications and injecting codebase standards into coding-agent workflows. It does not claim to be an execution sandbox. In all three cases, the deciding question remains concrete: which process runs generated code, under which OS identity, with which mounts, network paths, credentials, resource limits, and stop mechanism?

OpenShell separates the sandbox from the policy decision

NVIDIA OpenShell was the closest match I found to the provider boundary I wanted to explore. It is not another agent. It is an open-source runtime and control plane for running agents under declarative filesystem, process, network, and provider policies.

The architecture separates an untrusted workload from a trusted supervisor. The workload has no direct network egress. DNS lookups and TCP connections travel over an authenticated channel to the supervisor, which identifies the calling program, checks policy, and opens an approved connection. Provider credentials stay on the trusted side and are added only to requests for an authorized endpoint.

Filesystem restrictions use Landlock, while seccomp constrains process behavior and mediates network operations. A policy prover checks proposed network-policy changes for new risks, such as credentialed access to another host or access to cloud metadata. A flagged proposal waits for human review instead of being applied automatically.

The same policy model can sit above several compute drivers. Docker and Podman disable workload networking and connect the sandbox to its supervisor through a protected Unix socket. Kubernetes uses a private service with mutual TLS and a NetworkPolicy fence. The microVM driver gives the guest no network device and uses vsock for the supervisor channel. OpenShell therefore does not replace the underlying isolation technology; it supplies a common mediation and lifecycle layer above it.

Setting up OpenShell locally

The current support matrix lists Debian or Ubuntu on x86-64 and ARM64, macOS on Apple Silicon with Docker Desktop, and Windows through WSL 2 with Docker Desktop as experimental. A Docker-backed setup requires Docker Engine or Docker Desktop 28.0 or newer. Podman 5.x, Kubernetes 1.29, and the microVM driver are alternative backends.

The official quickstart installs the CLI and a local gateway, then creates a retained sandbox:

curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
openshell sandbox create --name demo

Without --from, the gateway uses nvcr.io/nvidia/base/ubuntu:24.04. That image is a minimal Ubuntu environment with no coding agent installed. The restrictive fallback policy allows the working directory and required system paths but denies all outbound network access.

The first-agent guide uses OpenCode with OpenRouter to show the additional pieces. Set OPENROUTER_API_KEY in the host shell, import the provider profile, and let OpenShell store the credential outside the workload:

export OPENROUTER_API_KEY="<your-key>"

openshell profile import \
  --url https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/openrouter.yaml

openshell provider create \
  --name openrouter \
  --type openrouter \
  --from-existing

openshell sandbox create \
  --name my-agent \
  --from ghcr.io/anomalyco/opencode:latest \
  --provider openrouter \
  -- opencode -m openrouter/nvidia/nemotron-3.5-lightning:free

When the agent requests a destination that policy denies, OpenShell records the denial and can draft a narrow rule. The host-side review loop is explicit:

openshell rule get my-agent --status pending
openshell rule approve my-agent --chunk-id <chunk-id>

Approved network rules hot-reload into the running sandbox. Logs and denied operations remain available through openshell logs my-agent, and openshell sandbox delete my-agent stops the workload, releases its resources, and purges injected credentials.

This setup is more involved than starting an unrestricted container, which is the point. The image defines the tools, the provider defines where a credential may be used, policy defines allowed access, and the gateway owns lifecycle and review. The main operational caveat is that its kernel controls have real prerequisites: the sandbox requires Landlock ABI 3 or newer and specific seccomp features. OpenShell probes those capabilities and fails closed when the boundary cannot be built.

How Autodev fits, and where it does not

Autodev currently uses per-task Docker containers for local development and Azure Container Apps custom dynamic sessions for Azure deployments. I chose Docker first because it made the system easy to run, inspect, and change while I was still learning what the execution contract needed to be.

Each task gets a separate Git worktree and execution environment. Local containers start without network access. A recognized dependency-install command can receive temporary bridge access only when both agent capability and policy allow it. The bridge is removed after that command. CPU, memory, process, user, capability, and no-new-privileges settings constrain the container. The worktree and evidence remain after the container is removed.

That design is useful for a local research workbench. It is not the boundary I would accept for hostile multi-tenant workloads. The control plane currently mounts the Docker socket, which gives it broad host authority. A hosted version needs a narrow sandbox broker, workload identity, and a stronger provider. A microVM service or managed equivalent is the direction I would evaluate, not a feature Autodev already has.

The useful part of bringing execution into Autodev was defining a provider boundary. The workflow can ask for a workspace, execute a typed command, change network state, collect evidence, and clean up without making the agent itself the sandbox manager. Docker and Azure implement that contract differently. A future provider should be replaceable without changing the policy or evidence model.

This integration also exposed a limitation in neat architecture diagrams: the control plane is part of the attack surface. Moving a workload into a microVM does not help if the broker accepts arbitrary host paths, hands secrets directly to the guest, or cannot terminate a stuck run. The broker API deserves the same threat modeling as the guest.

My verdict: choose the whole boundary, not the runtime label

I would not ask whether containers or microVMs are “the” answer for coding agents. Containers remain a practical choice for trusted local work and controlled CI tasks. gVisor offers an interesting middle layer when compatibility permits it. Kata and Firecracker-based systems make more sense as tenant trust decreases, provided someone operates the missing infrastructure. Managed sandboxes can be the best engineering choice when their controls and evidence meet the organization’s needs.

The decision should start with the workload and attacker model, then cover process, filesystem, network, credentials, resources, lifecycle, evidence, and control-plane privilege. If any of those rows is left implicit, the sandbox name says less than it appears to.

Autodev was useful to me because it forced those rows into one execution contract and made its weak points visible. Its current Docker implementation is a development compromise, not the conclusion of the research.

The next part of this series will cover agent governance and evaluation: why an agent needs an identity distinct from its user’s credential, how policy should mediate semantic capabilities, and why evaluation belongs in the promotion path. The final planned part is user experience and the collaboration space, including evidence review, intervention, approval, and handoff.

#agents#agentic-sdlc#sandboxing#containers#microvm#security