The Hidden Dangers of Letting LLMs Execute Code Directly
Giving AI the ability to run code unlocks immense power. But it can also quickly become your worst security nightmare.
With just a subtle logic bug or a clever prompt injection, the model could inadvertently execute os.system("rm -rf /"). Even worse, it could exfiltrate environment variables containing database credentials, or spin up an infinite loop that spikes server CPU to 100%.
To isolate the execution environment, engineering teams typically weigh three approaches:
- Self-hosting Docker Containers: The backend invokes the Docker daemon to spin up a new container for each request, injects the code, and destroys the container upon completion.
- Managing MicroVMs (Firecracker / gVisor): Provides complete kernel isolation at the hardware virtualization layer, mirroring the architecture behind AWS Lambda.
- Dedicated Cloud Sandboxes (E2B Code Interpreter): A fully managed microVM infrastructure accessible via an SDK. Linux virtual machines boot in 150–200ms, come pre-loaded with Python and a Jupyter kernel, and support instant file uploads/downloads via API.
Field Comparison: Docker, Firecracker, or E2B?
We tested all three options extensively before locking in our production architecture.
1. Self-hosting Docker Engine
Pros: Zero licensing costs, runs on existing servers, and makes it trivial to bake proprietary libraries into custom images.
Cons: Heavy lifecycle management overhead. Docker cold-starts typically take 1.5 to 3 seconds. If 100 concurrent users trigger code runs, disk I/O chokes instantly. Most critically: Docker shares the host’s Linux kernel. A single container breakout exploit like CVE-2024-21626 is all it takes for an attacker to compromise the entire host machine.
2. Building a MicroVM Cluster with Firecracker
Pros: Bulletproof security at the hardware virtualization level. Ultra-fast boot times (under 50ms).
Cons: Staggering DevOps maintenance. You must build and operate virtual networking, guest disk cleanup mechanisms, IP pooling, and 24/7 cluster monitoring from scratch. If your team has fewer than 5 engineers, maintenance overhead will quickly derail your roadmap.
3. E2B Code Interpreter
Pros: Frictionless integration. Sandboxes spin up in ~150ms. The environment features an out-of-the-box Jupyter runtime that cleanly separates stdout, stderr, and chart artifacts (PNG/SVG) as base64 strings. You never have to worry about kernel patching or infrastructure scaling.
Cons: Dependent on E2B cloud infrastructure. If you handle healthcare or financial records that mandate 100% on-premises data residency, you will need to contact their team for a self-hosted enterprise deployment.
When Should You Choose Which Solution?
Building an internal bot for a few colleagues to test? A Docker container tightly throttled with --memory=512m and --cpus=1.0 will do the job.
However, when exposing code execution to external paying customers, the stakes are completely different. A single security breach or service outage can destroy customer trust overnight. In this scenario, E2B saves you weeks of infrastructure groundwork while maintaining low latency and rock-solid isolation.
Step-by-Step Guide: Integrating E2B Code Interpreter in Python
Step 1: Install the SDK and Obtain an API Key
Create a free account at e2b.dev to obtain your API key. Next, install the SDK into your development environment:
pip install e2b-code-interpreter python-dotenv
Create a .env file in your project root:
E2B_API_KEY=e2b_your_actual_api_key_here
Step 2: Run Your First Code Snippet in the Sandbox
The with Sandbox() context manager automatically releases compute resources once the execution block completes:
import os
from dotenv import load_dotenv
from e2b_code_interpreter import Sandbox
load_dotenv()
# Initialize an isolated sandbox
with Sandbox() as sandbox:
code = """
import math
def calculate_primes(limit):
primes = []
for num in range(2, limit):
if all(num % i != 0 for i in range(2, int(math.sqrt(num)) + 1)):
primes.append(num)
return len(primes)
count = calculate_primes(1000)
print(f'Found {count} prime numbers under 1000.')
"""
print("Sending code to E2B sandbox...")
execution = sandbox.run_code(code)
# Print stdout results
for log in execution.logs.stdout:
print("Output:", log)
# Catch syntax or runtime errors
if execution.error:
print("Error:", execution.error.name, execution.error.value)
Step 3: Upload Data Files and Export Analysis Charts
AI agents frequently need to parse user-uploaded CSV files, run analytical transformations, and plot figures. E2B handles this effortlessly thanks to its bidirectional file I/O capabilities:
import base64
from e2b_code_interpreter import Sandbox
with Sandbox() as sandbox:
# 1. Write the CSV file directly into the sandbox
csv_content = "Month,Revenue\n1,120\n2,150\n3,180\n4,220\n5,210\n6,290"
sandbox.files.write("data.csv", csv_content)
# 2. Plotting script using matplotlib
analysis_script = """
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('data.csv')
plt.figure(figsize=(8, 4))
plt.plot(df['Month'], df['Revenue'], marker='o', color='royalblue')
plt.title('First Half Revenue')
plt.xlabel('Month')
plt.ylabel('Million VND')
plt.grid(True)
plt.show()
"""
print("Running data analysis...")
execution = sandbox.run_code(analysis_script)
# 3. Retrieve PNG images (returned as base64 strings) and write to disk
for i, result in enumerate(execution.results):
if result.png:
with open(f"chart_{i}.png", "wb") as f:
f.write(base64.b64decode(result.png))
print(f"Chart successfully exported: chart_{i}.png")
Step 4: Connect E2B to the LLM Tool Calling Loop
To build a production-grade autonomous agent (powered by Claude 3.5 Sonnet or GPT-4o), wrap E2B as a code execution tool. The end-to-end loop runs in 5 distinct steps:
- The user sends a request: “Plot a quarterly revenue breakdown chart for me.”
- The LLM generates the corresponding Python script via a tool call.
- The backend intercepts the code string and passes it into
sandbox.run_code(). - If execution succeeds, the stdout logs or generated chart assets are returned to the LLM to formulate its final answer.
- If an exception occurs, stream the traceback log back to the LLM. The model will parse the error, patch the script, and attempt a retry (self-debugging).
3 Critical Rules for Production Deployments
Do not go to production without implementing these three essential practices:
- Always enforce strict timeouts: LLM-generated code frequently falls into unintentional infinite loops. Always configure
run_code(code, timeout=30)to automatically abort after 30 seconds, preventing runaway compute billing. - Reuse sandboxes per session: Do not spin up a brand new sandbox for every single prompt. Maintain the
sandbox_idthroughout the user’s active session. This retains in-memory state, DataFrames, and previously uploaded working files. - Proactively terminate sandboxes: When a user navigates away or their session expires, call
sandbox.kill(). Never leave idle microVMs running in the background running up unnecessary costs.
