DEV Community

Cover image for How to Safely Run AI-Generated Code: E2B, Modal and Piston in Python
Moksh Gupta
Moksh Gupta

Posted on • Originally published at devtoollab.com

How to Safely Run AI-Generated Code: E2B, Modal and Piston in Python

The moment you let an LLM write code and then run it for real, you have handed a decision to a model that has no idea what your .env file contains. A chatbot that only talks is safe by construction. One that executes its own Python to check its math or plot a chart is a different animal, and something has to decide where that code is allowed to run.

Most people's first instinct is exec(), and that is exactly the wrong instinct: exec() runs inside your own process, with your environment variables and your filesystem permissions along for the ride. I wrote a longer version of this walkthrough on DevToolLab, covering E2B, Modal, and Piston, the open source engine you can self-host instead of depending on either vendor. Every snippet below was checked against the SDK versions actually installed via pip, not copied from a marketing page.

The exec() problem, quickly

Drop a model-generated string straight into exec() and it can read anything your process can read:

model_output = """
import os
print(f"I can see {len(os.environ)} environment variables from this process.")
"""
exec(model_output)
Enter fullscreen mode Exit fullscreen mode

On an ordinary laptop that prints something like I can see 61 environment variables from this process, and nothing about that call needed elevated permissions. It is just a normal Python process reading its own environment, which is usually where API keys and secrets live. A sandbox exists to give the model's code its own throwaway machine so that reading os.environ returns nothing interesting.

E2B: microVMs built for this exact loop

E2B runs each sandbox as a Firecracker microVM, the same virtualization AWS built for Lambda. Install the code interpreter SDK, grab an API key from the E2B dashboard, and export it as E2B_API_KEY:

pip install e2b-code-interpreter
Enter fullscreen mode Exit fullscreen mode
from e2b_code_interpreter import Sandbox

with Sandbox.create(timeout=60) as sandbox:
    execution = sandbox.run_code("data = [4, 8, 15, 16, 23, 42]\nprint(sum(data))")
    print(execution.text)
Enter fullscreen mode Exit fullscreen mode

Always set an explicit timeout. run_code() hands back an Execution object, not a plain string, with .text for the printed result, .results for richer outputs like PNGs from matplotlib, .logs for stdout/stderr, and .error for a structured exception (.name, .value, .traceback) instead of a thrown Python exception. Checking .error is the idiomatic move here, since a failing snippet is not a transport failure, it is a normal result the SDK reports back correctly.

E2B's Hobby tier is free with a one-time $100 usage credit, 20 concurrent sandboxes, and one-hour sessions. Pro is $150/month, stretching sessions to 24 hours and concurrency to 100.

E2B's pricing page showing the free Hobby tier and the $150/mo Pro tier

Modal: containers instead of microVMs

Modal takes a different route: a Sandbox is a container on Modal's general compute platform, so the same image-building tools you'd use for a Modal function work here too.

pip install modal
modal setup
Enter fullscreen mode Exit fullscreen mode

modal setup opens a browser and writes a token to ~/.modal.toml, no manual API key needed. A Modal sandbox starts idle with no entrypoint, and you run commands inside it with .exec():

import modal

app = modal.App.lookup("demo", create_if_missing=True)
image = modal.Image.debian_slim(python_version="3.12")
sandbox = modal.Sandbox.create(image=image, app=app, timeout=60, block_network=True)

process = sandbox.exec("python", "-c", "print(sum([4, 8, 15, 16, 23, 42]))")
print(process.stdout.read())
sandbox.terminate()
Enter fullscreen mode Exit fullscreen mode

Sandbox.create() defaults its own timeout to 300 seconds if you skip it, and block_network=True is worth setting explicitly for anything that only needs to compute, not call out. Modal's Starter plan is free with $30/month in credits and no idle charges; Sandboxes bill at roughly 3x the standard per-second rate since they're non-preemptible.

Piston: the open source, self-hosted option

Piston is a genuinely open source (MIT licensed) code execution engine that's powered Discord code bots for years. Its public demo API used to be free to hit, but as of February 15, 2026 the execute endpoint went whitelist-only, confirmed directly:

curl -s -X POST "https://emkc.org/api/v2/piston/execute" \
  -H "Content-Type: application/json" \
  -d '{"language":"python","version":"3.10.0","files":[{"name":"main.py","content":"print(1)"}]}'
Enter fullscreen mode Exit fullscreen mode

That returns a message pointing you to self-hosting instead. The good news is Piston ships a ready Docker image:

docker run --privileged -v $PWD:/piston -dit -p 2000:2000 --name piston_api ghcr.io/engineer-man/piston
Enter fullscreen mode Exit fullscreen mode

Once it's running, point requests at your own host instead of emkc.org, using the exact same request shape:

import requests
response = requests.post(
    "http://localhost:2000/api/v2/execute",
    json={"language": "python", "version": "3.10.0", "files": [{"name": "main.py", "content": "print('hi')"}]},
)
print(response.json()["run"]["stdout"])
Enter fullscreen mode Exit fullscreen mode

Piston sandboxes each execution with isolate, the same tool competitive-programming judges use, but you own patching the host and scoping container privileges yourself, work E2B and Modal absorb as part of the price.

The engineer-man/piston GitHub repo showing 2.8k stars and its MIT license

Picking one

If you want the least infrastructure to think about, start with E2B, its run_code() model is purpose-built for this loop. If you need a custom image, mounted volumes, or you're already deploying on Modal, its container-based Sandbox fits naturally. If neither vendor works for your situation, Piston self-hosted is the real fallback, at the cost of running it yourself. Whichever you pick, the full DevToolLab guide has the complete code for all three plus the exact pricing math, and DevToolLab's .env File Generator is a quick way to scaffold the file holding your API keys without hand-typing it.

The pattern that matters more than which SDK you choose: always set an explicit timeout, always check the structured error instead of guessing, decide deliberately whether the sandbox gets network access, and always tear it down when you're done.

References

Top comments (0)