Why a code tool is different from every other tool
Every other tool has a fixed surface. order_status can do exactly one thing, and the worst a wrong call achieves is the wrong order id.
A code-execution tool has no surface. run_python(code) accepts arbitrary programs, which means its capability set is whatever the process it runs in can reach: the filesystem, the network, environment variables, other processes, your cloud metadata endpoint. You have not added a tool. You have added a shell.
That is also why it is valuable. Models are poor at arithmetic and excellent at writing the code that does arithmetic. A model asked to compute a weighted median over 4,000 rows will produce a confident wrong number; asked to write the pandas that computes it, it will usually be right, and the answer is verifiable because you can read the code.
So it is worth having. It is not worth having on the machine your application runs on.
The layers, from weakest to strongest
A restricted interpreter: stripping builtins, blocking imports, walking the AST. This has been broken repeatedly for decades in every language that has tried it. Python’s introspection makes escape routes easy to find and hard to enumerate. Do not rely on it as your only boundary.
A separate process with resource limits. setrlimit on CPU, memory and file descriptors, a timeout, a low-privilege user. Stops runaway loops. Does not stop reading files or making network calls.
A container. A fresh container per execution, no mounted volumes, read-only root filesystem, dropped capabilities, no network, a non-root user, memory and CPU caps, killed after N seconds. This is the practical baseline and it is achievable with Docker or Podman in an afternoon.
A sandbox with a stronger kernel boundary: gVisor, Firecracker microVMs, or WebAssembly runtimes such as Pyodide in a worker. Container escapes via kernel bugs are rare but real; these narrow that. WASM is the interesting cheap option: Pyodide runs Python in the browser or in a Wasm runtime with no syscalls at all, which makes the sandbox the default rather than something you configure.
Someone else’s sandbox. Several providers offer a hosted code interpreter. You have outsourced the hard part, and you have sent them your data.
The settings that matter most
If you take four things from this lesson:
No network by default. This is the highest-value control by a wide margin. Without egress, code cannot exfiltrate data, cannot fetch a second-stage payload, and cannot reach 169.254.169.254 to steal your cloud instance credentials. Where the task needs a specific host, allowlist that host; never open it generally.
No credentials in the environment. Do not pass your API keys, database URLs or cloud role into the sandbox. An empty environment costs nothing and removes the prize.
Ephemeral and disposable. A fresh container per run. State that persists between runs is state an earlier run can leave behind for a later one.
Hard timeout and memory cap. Enforced by the runtime, not by a signal handler inside the code you are trying to constrain.
Data goes in as data
The useful pattern is not “run this code on the server”. It is: copy the specific input into the sandbox, run, copy the output back.
with Sandbox(network=False, mem_mb=512, timeout_s=20) as sb:
sb.write("/work/data.csv", csv_bytes) # only what is needed
out = sb.run(code, cwd="/work")
result = sb.read("/work/out.json") if out.ok else NoneCopy in the one file, not the directory. Read back one named artefact, not whatever appeared. Cap the size of both. A model that writes a 4 GB file has otherwise just filled your disk.
Show the code
A property that makes code execution unusually trustworthy compared with other model outputs: the working is inspectable. When an assistant reports a revenue figure, you cannot check its reasoning. When it reports a figure and shows the eleven lines of pandas that produced it, a competent colleague can audit it in thirty seconds.
So surface the code, always, next to the result. It converts an opaque claim into a reviewable one, and it makes the failure mode legible: a wrong answer usually comes with visibly wrong code, which is the easiest kind of bug to report.
One caveat to be honest about: a model can write code that runs cleanly, produces a number, and answers a subtly different question than the one asked: filtering on the wrong column, silently dropping nulls, joining on a key with duplicates. The code being visible is what gives you any chance of catching that. It is not a guarantee that anyone will look.
Every lesson here is open. Your progress is recorded once you pass the Python, from Zero, for AI exam.
Check your understanding
A data assistant runs model-written Python in a container with a CPU limit, a memory cap and a 30-second timeout, but with network access left on. What is the largest remaining exposure?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Sayali, your AI tutor
