Sandboxing AI Agent Code Execution in Python: Containers, MicroVMs and Beyond

In this article, you will learn how to sandbox the code an AI agent writes by running it in a limited subprocess, a hardened Docker container, and a Firecracker microVM, and how to compare their safety and latency.

Topics we will cover include:

  • How to give a code-writing agent a single execution tool backed by interchangeable sandbox backends.
  • How subprocess limits, Docker flags, and E2B microVMs each contain CPU, memory, process, disk, file, and network attacks.
  • How to test every tier against the same hostile programs and measure the latency cost of stronger isolation.

Sandboxing AI Agent Code Execution in Python: Containers, MicroVMs and Beyond

An agent that writes and runs code is only as safe as the place that code runs. Sandboxing AI agent code execution means putting a boundary around that place, and the boundary you choose decides what a bad program can reach. This article gives a code-writing agent a real execution tool, then runs it three ways: a limited subprocess, a Docker container, and a Firecracker microVM. You will test each against the same hostile programs and compare safety with latency. If you would rather start from hosted options, check out this article that covers five code sandboxes for your AI agents. Here we build the layers ourselves, so you can see what those platforms do underneath.

Three Isolation Levels

Untrusted code can eat resources, read secrets, call internal services, or break out of its boundary. The tiers differ in where that boundary sits. A subprocess is the lightest option: a child process with limits on CPU, memory, and file size, sharing the host’s kernel and files. A container adds namespaces and cgroups, yet it is still a set of processes on the host’s kernel, which is why Docker’s own docs note that a container has no resource limits unless you set them. Escapes happen: in November 2025, three runc vulnerabilities let attackers break out of containers. A microVM gives each sandbox its own kernel behind a hypervisor. Docker describes this as a hardware boundary, and AWS says its Firecracker-based Lambda MicroVMs share no kernel between sessions. One more point from Docker’s post applies to every tier: file access, network policy, and secrets should be defined before the agent runs, by infrastructure and not by the agent.

Subprocess-Container-MicroVM

Subprocess-Container-MicroVM
Image by Author

Setup

You need Python 3.10 or newer. The subprocess tier uses Linux features (resource limits and unshare), so run it on Linux or in WSL. The Docker tier needs Docker installed. The microVM tier needs an E2B account and API key. The agent loop needs an Anthropic API key, but you can skip it and go straight to the tests.

The first line installs both SDKs. The second downloads the container image now, so the first run doesn’t include the download. The last two store your keys, which the SDKs read from the environment. Put the four files below in one folder: sandbox.py, agent.py, attacks.py, and bench.py.

Step 1: Give the Agent an Execution Tool

The agent gets one tool, run_python. Every call goes through a sandbox object, so changing tiers never touches the agent.

TOOL describes run_python to the model: it takes a string of code and returns stdout, stderr, and an exit code. run_agent loops. It sends the conversation, and when the model asks for the tool, it hands the code to sandbox.run and sends the result back as a tool_result, flagged as an error when the exit code is not zero. The model’s code never runs in this process, and anything with a run(code) method works as the sandbox.

Step 2: Tier One, a Limited Subprocess

The first part of sandbox.py holds the shared result type and the subprocess backend.

Result is what every backend returns. The output is trimmed, and SAFE_ENV leaves out your API keys and home directory. In SubprocessSandbox, _apply_limits runs in the child before the code starts. It caps CPU time, memory (address space), and the size of any file it writes. If you run as root, it first drops to the unprivileged nobody user (clearing root’s supplementary groups) and adds a process limit. That limit counts all processes of a user, so it only makes sense for a dedicated user, and on a normal laptop it is skipped.

run makes a fresh temp folder and runs python -I (isolated mode) inside it, wrapped in unshare –net, which gives the child a private network with nothing in it. The constructor tests that unshare works and raises an error if not, so the sandbox never silently falls back to an open network. Output goes to files, so the file-size limit also stops print floods. A timeout kills the whole process group, and the temp folder is deleted afterward. This tier does not hide your files: the code runs as you, on your filesystem.

Step 3: Tier Two, a Docker Container

The second part adds the container backend.

_command builds the docker run line, and each flag closes one door. –network none removes the network. –read-only makes the root filesystem unwritable, with a small 16 MB /tmp for scratch. –cap-drop ALL and no-new-privileges remove special privileges, and –user avoids root. The resource flags are the ones in Docker’s docs: –memory plus an equal –memory-swap stops the container from using swap, and –cpus and –pids-limit cap CPU and process count. The program arrives on stdin, so no host folder is mounted. On a timeout, docker kill stops the container, because killing the docker client alone would leave it running.

Step 4: Tier Three, a MicroVM

The last part wraps E2B, whose sandboxes are Firecracker microVMs.

E2BSandbox creates a new sandbox for every run, so nothing carries over. The with block destroys the VM when it ends. run_code returns the logs and an error object, per E2B’s SDK reference, and we copy them into the same Result. The SDK raises an exception when code exceeds its timeout, and we convert that into timed_out. get_backend picks a tier by name.

Step 5: Run the Hostile Programs

Now we test the boundaries. The same six attacks run against any backend.

The script plants a fake credentials file and starts a listener on the host’s loopback as a stand-in for an internal service. Each attack is a program plus a word that appears in its output only if the attack worked. The six attacks are an infinite loop, a 512 MB memory grab, a bounded fork flood of 200 processes, a 50 MB file write, a read of the fake secret, and a connection to the local service. The fork flood is capped so it can’t hurt your machine. Blocked means the marker word never appeared, or, for the loop, that the run timed out.

This is the subprocess tier run as a normal Linux user. Four attacks are blocked, and two get through. The fork flood is allowed because the process cap only applies to a dedicated user, and the secret file is readable because the code runs as you. Both are real limits of this tier, and they are why it suits only code you mostly trust. Run –backend docker and –backend e2b next. With the flags above, Docker should block all six: the container has no network, its filesystem doesn’t contain your files, and the memory, process, and disk limits apply. In a microVM, memory, process, and disk attacks may run to the limits of the VM you chose, but they stay inside it, and the secret file and local service don’t exist there.

Step 6: Compare Latency

Safety costs time, so measure it.

The script runs print(1) ten times on a backend after one warm-up run and prints the median and slowest times. For E2B, each run creates a microVM, so the time includes startup. Run it for each backend on your machine:

Expect the subprocess to be fastest, since it only starts Python. For the others, published figures are claims, not guarantees: one write-up reports that E2B boots in about 150 ms, and both AWS and Docker describe near-instant launches for their microVM products.

Choosing a Tier

Use a limited subprocess only for code you trust, such as your own scripts in development. Use a hardened container when you control the host and the stakes are low, and keep runc patched. Use a microVM for code from many users, for anything that touches secrets, and when the agent needs more than Python, since Docker points out that agents often need to build and run containers themselves, which plain containers handle poorly.

Conclusion

The direction is clear. Docker launched Docker Sandboxes on microVMs in April 2026, and AWS now offers Lambda MicroVMs, which keep state for up to 8 hours and suspend when idle. My expectation, an opinion and not a forecast, is that microVMs will become the default boundary for agent code, with the policy set by infrastructure and not by the model.

To recap: an agent’s code needs a boundary, and each tier moves it. A subprocess with limits is fast but shares your files and kernel. A hardened container adds network, filesystem, and resource controls on a shared kernel. A microVM gives each run its own kernel. You built all three behind one run(code) interface, tested them against the same six attacks, and have a script to measure the latency cost. Pick the lightest tier that matches how much you trust the code, and keep the same attack suite in your tests.

No comments yet.

Leave a Reply

Machine Learning Mastery is part of Guiding Tech Media, a leading digital media publisher focused on helping people figure out technology. Visit our corporate website to learn more about our mission and team.