Back to Blog
Stacked shipping containers representing isolated, disposable sandboxes for AI agent tool execution

AI Agent Sandboxing: Securing Tool Execution

12 min read

AI agent sandboxing becomes necessary as soon as a workflow can execute generated code or shell commands. A bad command might read host files, contact an internal service, consume all available memory, or leave state that changes the next run. Prompt rules cannot stop those outcomes because the model is already operating inside the boundary it was told not to cross.

The fix is a disposable execution cell with an enforceable contract. Give each run only the files, network destinations, credentials, resources, and time it needs. Capture the result, destroy the cell, and test denied actions as seriously as successful ones. This guide lays out that complete lifecycle for platform and security engineers.

Start with the boundary you need to protect

Do not begin by choosing a container product. Begin with the assets and paths that must remain outside the agent's authority. Typical protected assets include the host filesystem, cloud metadata endpoints, control-plane APIs, internal databases, source repositories unrelated to the task, and credentials held by the workflow service.

Next, identify every way executed code could reach those assets. The obvious paths are filesystem mounts and outbound network connections. Less obvious paths include inherited environment variables, shared process namespaces, writable container sockets, cached package credentials, persistent work directories, and excessive kernel access.

This distinction matters because a sandbox is not one switch. The gVisor security model explains that isolating untrusted userspace reduces exposure to the host system interface, but also states that a sandbox is not a substitute for a secure architecture. Runtime isolation, business authorization, network controls, and lifecycle cleanup solve different parts of the problem.

Write a short threat statement before implementation. For example:

Code produced or selected by the model is untrusted. It may attempt to read data outside the task, modify persistent state, contact an unapproved destination, exhaust resources, or exploit the execution runtime. The workflow must contain those actions without relying on model cooperation.

That statement gives reviewers a concrete standard. It also prevents the design from drifting into a vague promise that the agent is "safe."

Treat model output as untrusted control input

A model can produce a dangerous command because of an ambiguous request, a hallucinated path, poisoned retrieved content, or a direct prompt injection. The source does not change the required control. The command has to pass through policy and then run inside an environment that can tolerate hostile behavior.

OWASP describes excessive functionality, permissions, and autonomy as root causes of Excessive Agency. Sandboxing addresses the execution side of that risk, but it does not justify giving the workflow broad business permissions. Keep the tool surface narrow before the runtime sees a command.

Evaluate the request in order. First ask whether the task needs shell or code execution. If it does, check whether its requested operation is allowed for that workload class. An admitted command still runs inside runtime containment that prevents it from exceeding its intended authority.

A command denylist cannot replace the third decision. Shell syntax, interpreters, package hooks, and nested tools create too many equivalent paths. Command policy can reject obviously forbidden operations and route risky jobs for approval, while the execution boundary must remain effective if policy misses something.

OpenAI's shell tool guidance makes the same practical point: arbitrary shell commands are dangerous, so execution should be sandboxed, constrained with allowlists or denylists where useful, and logged for audit. Treat that as a minimum, not a complete architecture.

Define a per-run execution contract

The workflow should create an explicit contract before starting a runtime. Avoid a general "agent sandbox" configuration shared by every task. A document conversion job and a repository test job need different files, binaries, network access, and limits.

A compact contract might look like this:

execution_contract:
  image: document-tools@sha256:pinned-image-digest
  identity:
    uid: 10001
    gid: 10001
  filesystem:
    readonly_root: true
    readonly_inputs:
      - input/report.docx
    writable_paths:
      - output/
      - tmp/
    output_allowlist:
      - output/report.pdf
  network:
    mode: deny
    allowed_domains: []
  resources:
    cpu_millis: 1000
    memory_mb: 768
    process_limit: 64
    wall_time_seconds: 90
    output_bytes: 5242880
  credentials: []
  cleanup:
    destroy_runtime: true
    retain_outputs_only: true

The workflow service owns this contract. The model may request capabilities, but it must not set its own limits, choose arbitrary mounts, add destinations, or disable cleanup. Any exception should come from a trusted policy layer using known task metadata.

Pin the execution image by digest rather than a mutable tag. Keep the image small and include only approved interpreters and tools. A smaller runtime does not guarantee security, but it reduces accidental capability and makes the environment easier to patch, scan, and reproduce.

Build a disposable execution cell

Create a new cell for each trust boundary. For short tasks, that usually means one runtime per tool call. For a multi-step job that genuinely needs shared temporary state, one cell can serve the job, but it should not be reused for unrelated users or workflows.

The baseline configuration should include:

  • an unprivileged user with no path to elevate privileges;
  • a read-only root filesystem;
  • explicit read-only input mounts;
  • a small writable temporary area;
  • no host process, network, or IPC namespaces;
  • no container runtime socket or control-plane credential;
  • dropped Linux capabilities;
  • a syscall filter;
  • resource and time ceilings;
  • destruction after output collection.

Containers supply useful process and filesystem isolation, but do not assume the default configuration equals a hostile-code sandbox. Docker documents that its default seccomp profile is a syscall allowlist intended to support least-privilege container execution. Keep that profile unless compatibility testing proves a narrower custom profile is required. Never disable syscall filtering just to make one package install succeed.

For workloads with a stronger hostile-code threat, add another isolation layer such as a userspace kernel or a lightweight virtual machine. The right choice depends on acceptable startup time, workload compatibility, host exposure, and operational maturity. Record that decision against the threat statement instead of claiming that every container or virtual machine provides identical protection.

Keep credentials outside the cell by default

A sandbox with a powerful cloud token is still powerful. Do not copy the workflow service's environment into the execution runtime. Start with an empty credential set and add a capability only when the task contract requires it.

Prefer a broker pattern for business actions. The sandbox produces a structured request such as "store this generated PDF for case 4821." The trusted workflow service validates the request, checks user and workload authorization, and performs the write with a short-lived credential. The agent never receives the underlying storage key.

If code inside the cell must call an external service directly, issue a short-lived token scoped to one audience and narrow actions. Deliver it at runtime, keep it out of logs, and revoke or let it expire when the cell ends. A token lifetime should fit the task timeout rather than the duration of the worker process.

Business-system permissions decide what an approved action may do. The sandbox decides what untrusted code can touch while trying to produce that action. Both controls are necessary because a narrowly authorized storage request can still be prepared by code that reads the wrong local file.

Deny network access unless the task proves it needs it

Outbound access turns a local execution mistake into a data exfiltration or internal discovery path. Start with no network. Enable only named destinations required by the task, and route traffic through an enforcement point that records the actual destination.

OpenAI's hosted shell documentation provides a useful concrete model: hosted containers have no outbound access by default, and enabling it requires both an administrative allowlist and an explicit request policy. Its guidance also warns that network-retrieved content may contain prompt injection and recommends reviewing requested hosts and actual outbound destinations.

A domain allowlist needs careful resolution. Validate the hostname before connection, resolve through a controlled resolver, and reject private, loopback, link-local, and cloud metadata addresses. Recheck the destination after redirects. If an allowed domain can proxy arbitrary URLs, the allowlist is not meaningful.

Package installation deserves its own workload class. It introduces install scripts, dependency substitution, and a broad network surface. Prefer images with dependencies already installed. If dynamic packages are unavoidable, use an approved registry mirror, lock versions and hashes, separate the build step from the run step, and discard the build environment afterward.

Enforce resource and output ceilings

A tool run can cause an outage without escaping. Recursive processes, decompression bombs, huge logs, and infinite loops all stay inside the runtime while consuming shared capacity.

Set limits at several layers:

  • CPU quota controls sustained compute use.
  • Memory limits stop one job from exhausting the host.
  • Process limits restrict fork storms.
  • Filesystem quotas constrain temporary growth.
  • Wall-clock time ends blocked or looping commands.
  • Output limits prevent logs and generated files from overwhelming the workflow service.

The outer orchestrator must enforce the deadline. A timeout implemented only inside the sandbox can be ignored or broken by the process being timed. On expiry, send a termination signal, allow a short fixed grace period if cleanup is safe, then destroy the runtime from outside it.

Treat truncation as a result state. If output exceeds the limit, record the limit that fired and preserve a bounded tail or structured summary. Do not pass a massive partial stream back into the model and hope it infers what happened.

Capture results, then destroy the runtime

Return only declared outputs. The workflow should not upload the whole writable filesystem because it may contain package caches, copied inputs, temporary credentials, or attacker-created paths.

A safe completion sequence is:

  1. Stop accepting new processes in the cell.
  2. Record exit status, timeout state, resource usage, policy denials, and output truncation.
  3. Validate output names, types, sizes, and path containment.
  4. Copy only allowlisted artifacts to trusted storage.
  5. Destroy the runtime and its writable volumes.
  6. Revoke task credentials and mark the execution contract complete.

Use a fresh identifier for each run and attach it to the workflow trace and audit event. Logs should capture policy decisions and outcomes without recording secrets or full sensitive file content.

Handle failures without weakening containment

A failed job should not automatically receive more authority. If a package is missing, do not rerun with host access. If the network call is denied, do not switch to unrestricted networking. If a syscall is blocked, do not disable seccomp globally.

Classify the failure first. A task error means the input is invalid or the command cannot produce the requested result. A policy denial means the task requested a forbidden file, destination, binary, or action. Capacity failures identify the CPU, memory, time, process, disk, or output ceiling that fired. Keep runtime faults separate from behavior that targeted the boundary or control plane, which should be treated as a suspected escape attempt.

Retry only runtime faults that are safe to repeat. Send task errors back with bounded diagnostics. Route policy denials through an explicit capability review. Quarantine evidence from suspected escape attempts and rotate any credential that might have been exposed.

The 2026 Hacker News discussion about AI agent sandboxes shows the practitioner tension clearly. Commenters describe needing useful filesystem or network access while worrying that custom sandboxes are easy to bypass. That thread is practitioner evidence, not proof of a universal failure rate. It does show why an exception process must be easier than quietly weakening defaults.

Verify the sandbox as a security boundary

A successful command proves usefulness, not containment. Add negative tests that attempt the actions your threat statement forbids. Run them in CI for every runtime image or policy change and periodically in production-like infrastructure.

At minimum, test that code cannot:

  • read an unmounted host file;
  • write outside declared output and temporary paths;
  • connect to an unapproved public domain;
  • connect to private, loopback, link-local, or metadata addresses;
  • inspect host processes or namespaces;
  • access a container runtime socket;
  • exceed process, memory, disk, output, or wall-time limits;
  • retain a file that appears in a later unrelated run;
  • export an undeclared artifact;
  • print injected credentials into retained logs.

Also test the permitted path. A sandbox that blocks the required conversion, build, or analysis will accumulate exceptions until it no longer protects anything. Measure startup latency, task completion, denial reasons, timeout frequency, and cleanup success by workload class.

Include one controlled boundary exercise. Place a harmless marker outside the mounted task directory, expose a fake internal endpoint, and verify that the test workload cannot read or contact either one. Confirm that the audit trail still records the denied attempts and the cell is destroyed.

Common mistakes to remove before rollout

A container is not complete containment. Review namespaces, mounts, capabilities, seccomp, network paths, credentials, control sockets, and host kernel exposure separately.

Do not use one broad profile for every workflow. A profile broad enough for repository builds will be excessive for document conversion, so generate contracts from trusted workload classes.

The model can describe a capability it needs, but it cannot approve its own exception. Trusted policy makes that decision.

Avoid persistent work directories. They carry poisoned dependencies and hidden state into later runs. Export declared artifacts and rebuild disposable state instead.

Happy-path tests are insufficient. Make denied reads, blocked connections, resource exhaustion, timeout cleanup, and cross-run residue part of the release gate.

Put one workload through the design

Choose one current internal workflow that executes code or shell commands. Inventory every input, output, binary, destination, credential, and resource it actually needs. Encode that inventory as a contract, run the permitted task, and then run the negative test suite.

Do not expand the profile until a denied requirement has an owner, a business reason, a narrower alternative, and a test. Once the workload passes both usefulness and containment checks, make its contract the template for similar jobs rather than a universal default.

Run this test next: disable outbound network access for that workload, mount only one test input and output directory, set hard resource limits, and try to read a harmless marker outside the mount. If the marker is reachable, the AI agent sandboxing boundary is not ready for production.

References


About Fire In Belly: Independent senior engineering from Tallinn, Estonia. We design and build AI workflow automation with the execution contracts, disposable sandboxes, and containment tests described above, at published fixed prices. Schedule a call to discuss your next project.