RSS Amplifier

The MLnotes Newsletter · Jun 15, 2026

Secure Playgrounds: Sandboxing & Execution Security in Harness Engineering

0
Sign in to vote or save

Mehdi Allahyari · The MLnotes Newsletter

In Part 2, we built a working agent harness with three real tools, read_file, write_file, and run_bash. The feedback loop worked. Errors came back as structured signals. The model self-corrected.

But I left something unaddressed on purpose, and it is time to confront it directly.

Our run_bash function was a subprocess.run() call on your local machine. No container. No isolation. No boundary between the model’s generated code and your host filesystem, environment variables, and network. If the agent wrote import os; os.environ into a script and executed it, it would see your API keys. If it ran curl to an attacker-controlled server, nothing would stop it.

This is not a hypothetical. In October 2024, security researcher Johann Rehberger demonstrated what he called the ZombAIs attack against Claude’s Computer Use feature. Using prompt injection embedded in a webpage the agent was browsing, he caused the agent to download and execute the Sliver C2 framework, effectively turning the AI into a remotely controlled zombie on the host machine, with no malicious intent from the user who launched it.

Giving an AI agent a shell is handing it a loaded weapon. Today we are going to look at how a harness builds a secure playground around that weapon.

Before containers or virtualization, one architectural principle must be non-negotiable: the agent harness must never run in the same execution context as the agent’s actions.

If your harness runs inside the sandbox, a compromised model execution can access your environment variables, modify your history logic, or exfiltrate your API keys. The harness is the warden; the sandbox is the cell. The warden must command from outside — passing instructions in, receiving exit codes and stdout back out.

Every sandboxing decision involves the same trade-off: isolation security vs. startup latency. Here is where the four primary models land in practice today:

A few clarifications worth calling out explicitly:

WebAssembly & Pyodide: The common claim that Wasm gives you sub-10ms startup is wrong for Python. Pyodide (Python compiled to Wasm) requires downloading ~6 MB of runtime and takes 1–3 seconds to initialize. Once running, it is also 3–5x slower than native CPython. The real advantage is mathematical isolation. Pyodide code cannot escape its Wasm memory boundary by design. It is a good fit for lightweight, browser-based execution but not for general-purpose agent tool calls.

Docker + gVisor: gVisor intercepts system calls in user space rather than passing them to the host kernel directly. This eliminates the biggest Docker security risk (kernel-sharing) while keeping Docker’s ergonomics. Google runs gVisor in production for Cloud Run. The tradeoff is ~10–20% runtime overhead and some syscall compatibility gaps.

Firecracker MicroVMs: Used by AWS Lambda and E2B (a cloud sandbox platform for AI agents processing ~15 million sandboxes/month as of 2025). Each agent gets its own kernel, not just a container namespace. Cold boot is ~90–200ms, and with VM snapshotting it drops to ~150ms for pre-warmed states. This is the production standard for hosted coding agents.

An agent refactoring a codebase needs file access. The question is which files.

Three rules make this work in practice:

1. Mount explicitly, never from root. Never bind-mount / or ~. Only mount the specific directory the agent is assigned to work in.

2. Use ephemeral copies for high-risk tasks. Copy the target repository into a temporary path (/tmp/agent-run-xyz) and mount that instead. When the agent finishes, diff the changes, present them to the user, then destroy the container. The original is never touched directly.

3. Run as a non-root user. Always run the container with a non-privileged user (--user 1000:1000). This prevents model-generated code from installing kernel modules, modifying network routes, or writing to system directories even if a container escape is attempted.

In Part 2, the run_bash function was a bare subprocess.run(). Here is what the upgrade looks like — a drop-in Docker replacement that applies all the isolation rules above:

Notice what each flag does:

  • --rm ensures the container is destroyed after each tool call, no state leaks between runs

  • --network none cuts off all external network access entirely

  • --read-only + --tmpfs means the agent can only write to /tmp and the mounted workspace, nothing else on the filesystem

  • --user 1000:1000 ensures model-generated code runs without root privileges

  • The double timeout (inner timeout 30 + outer timeout=35) guarantees the harness is never blocked by a runaway process

To plug this into the AgentHarness from Part 2, replace the run_bash function in TOOL_REGISTRY with a lambda that calls run_in_docker_sandbox with your workspace path.

If you do not want to manage Docker configuration yourself, several open-source libraries handle the heavy lifting. Here are three worth knowing, each covering a different point on the isolation spectrum:

E2B is the most production-ready option for AI agent sandboxing. Under the hood it uses Firecracker microVMs — each sandbox gets its own kernel. The Python SDK makes it a near drop-in replacement for the Docker function above:

Install with pip install e2b-code-interpreter. Requires an E2B API key. Best choice if you are building a cloud-hosted agent and want hardware-level isolation without managing infrastructure.

RestrictedPython takes a different approach — rather than isolating at the OS level, it restricts what Python code is allowed to do at parse time. You define exactly which builtins, imports, and operations are permitted before the code ever runs.

Install with pip install RestrictedPython. No containers needed — useful when Docker is overkill and you only need to prevent agents from importing os, subprocess, or sys. Not a replacement for OS-level isolation for untrusted code, but a solid lightweight layer for constrained use cases.

Monty is the most interesting new entrant in this space — a minimal, secure Python interpreter written in Rust by the Pydantic team, designed specifically for running LLM-generated code. It starts in under a microsecond, requires no containers, and completely blocks access to the host filesystem, environment variables, and network by default. You control exactly which host functions the agent can call.

Install with pip install pydantic-monty. The tradeoff is intentional scope — Monty runs a subset of Python and does not support third-party libraries like NumPy or Pydantic itself. It is designed for agents that express logic in pure Python rather than calling into ecosystem packages. Worth watching closely: Pydantic plans to use it as the foundation for code execution in PydanticAI, and it is still marked experimental at the time of writing.

If your agent executes JavaScript or TypeScript, Deno has a built-in permission model that makes sandboxing a flag, not an architecture decision:

Deno denies all filesystem, network, and environment access by default. You opt-in to exactly what the agent needs. Install with curl -fsSL https://deno.land/install.sh | sh. Best fit for TypeScript-first agent stacks.

Even with a containerized sandbox, unlimited internet access is a liability. A prompt-injected agent can participate in DDoS attacks, exfiltrate data, or, as ZombAIs demonstrated, phone home to a C2 server.

Two practical controls:

Allowlist over blocklist. Rather than trying to block malicious domains, restrict outbound traffic to a known-good set: pypi.org, npmjs.com, github.com, your internal registry. Everything else is denied by default. This is far more defensible than maintaining a blocklist.

Block cloud metadata endpoints. If your sandbox runs in a cloud environment (AWS, GCP, Azure), the instance metadata service at 169.254.169.254 is a prime target for SSRF attacks — a compromised agent can use it to steal IAM credentials. Block this at the network level and enforce IMDSv2 (require token-authenticated requests) at the cloud provider level. This was an active exploitation target as recently as early 2025.

Even inside an isolated container, an agent can write an infinite loop or spawn thousands of subprocesses:

  • CPU and memory caps: The --memory and --cpus Docker flags are your first line. Set them conservatively for agent tasks — 512 MB RAM and 1 CPU is sufficient for most code execution workloads.

  • Process limit: Add --pids-limit 100 to cap the number of processes the container can spawn. This stops fork bombs and runaway test runners.

  • Disk quota: Use Docker’s --storage-opt size=1G (with a supported storage driver) to cap how much the agent can write to the mounted workspace.

Some commands should never run without explicit approval, regardless of sandbox isolation. The harness is the right place to intercept them:

Wire this into the pre-execution hook from Part 2. If requires_human_approval() returns True, the harness pauses and prints the command for the user to approve or deny before the sandbox sees it. The agent never knows the gate exists — it just receives a response or a timeout.

A secure, sandboxed harness is now capable of running code safely over many iterations. But as tasks grow in complexity — refactoring a large codebase, researching a topic across dozens of sources, debugging a multi-file system — the agent will start to hit a different wall: context window bloat.

After 50 tool calls and thousands of lines of output, the conversation history becomes unwieldy, expensive, and eventually truncated. The agent starts to “forget” earlier decisions.

In Part 4 of this series, we will tackle Managing the Long-Running Agent: context compaction, dynamic history compression, and strategies for keeping an agent coherent across hours of execution without burning through your entire token budget in the first 20 minutes.

Thanks for reading The MLnotes Newsletter! This post is public so feel free to share it.

Share

Read the original on mlnotes.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.