{"course":{"slug":"agents-and-automation","title":"Agents and Automation","url":"https://addaly.com/learn/agents-and-automation","level":"intermediate","track":"build"},"lesson":{"slug":"sandboxing-what-it-runs","title":"Running code the model wrote","url":"https://addaly.com/learn/agents-and-automation/sandboxing-what-it-runs","position":42,"of":72,"minutes":9,"answer":"There is no in-process Python sandbox; isolate at the container or virtual-machine boundary, and turn the network off by default.","keyIdea":"There is no in-process Python sandbox; isolate at the container or virtual-machine boundary, and turn the network off by default."},"body":{"format":"markdown","text":"## One tool changes everything\n\nGive an agent a code-execution tool and every careful permission decision collapses into a single question, because arbitrary code can do whatever the process can do: read files, open sockets, spend money, and read the environment variables holding your other credentials.\n\nThis is still worth doing. Code is often the right tool: it does arithmetic properly, handles a thousand rows without them entering the context, and fails loudly. It just needs a boundary.\n\n## The layers, weakest to strongest\n\n**1. A restricted namespace in the same process.** Handing a stripped set of builtins to an eval call. This is **not a sandbox**. Python's object graph makes escapes routine: walking from any object up to its base class and back down through the list of subclasses to find something dangerous is a party trick, not research. Do not ship it, and be suspicious of any library that offers it as one.\n\n**2. A separate process, its own OS user, resource limits.** A genuine improvement: a crash is contained, and address-space and CPU limits bound the damage. Still the same kernel and the same filesystem, and the process can still open sockets.\n\n**3. A container, with the flags that matter.**\n\n```bash\ndocker run --rm \\\n  --network none \\\n  --read-only --tmpfs /tmp:size=64m \\\n  --memory 512m --cpus 1 --pids-limit 128 \\\n  --cap-drop ALL --security-opt no-new-privileges \\\n  -v \"$PWD/work:/work\" \\\n  python:3.12-slim python /work/task.py\n```\n\n`--network none` matters most, and it is the flag missing from every tutorial. `--pids-limit` stops a fork bomb. `--read-only` with a small tmpfs means the filesystem cannot be filled or persistently modified. `--cap-drop ALL` removes capabilities that almost nothing needs.\n\n**4. Kernel-level isolation.** gVisor, which puts a user-space kernel between the workload and the host, or Firecracker microVMs, which give each run its own kernel. This is what the hosted sandbox products are built on. Reach for it when you run untrusted code from many different users on shared hardware.\n\n**5. Somebody else's machine.** A hosted sandbox, so the blast radius is contractual rather than yours.\n\n## The three limits people forget\n\n- **Wall-clock time.** Wrap the command in a timeout. An infinite loop otherwise holds a slot forever.\n- **Output size.** A program printing in a loop fills your disk and then your context. Cap captured output at something like 100 KB, and say in the result that it was capped.\n- **Network.** Off unless proven necessary, then allowlisted. The default is on, and the default is wrong.\n\n## The mount that ruins the day\n\nMount one working directory. Never the repository, never the home directory, and never (this appears in tutorials with alarming frequency) the container runtime's own control socket. Access to that socket lets the container start a new privileged container mounting the host filesystem. That is not a sandbox with a hole in it; it is no sandbox.\n\n## The free path\n\nDocker with the flags above, on a server you already have. On Linux, `bubblewrap` and `firejail` are lighter and effective. A throwaway virtual machine you rebuild from a snapshot is crude and perfectly sound.\n\nPaid, and worth it at volume: **E2B**, **Modal**, **Daytona**, and the sandbox offerings from the major cloud providers. What you buy is warm-start latency, per-user isolation and somebody else's security team.\n\n## The test that matters\n\nWrite the escape yourself. Try to open a socket. Try to read the password file. Try to reach your cloud's instance metadata address. If your sandbox lets any of those through, you have found out now rather than from a bill."},"check":{"question":"A team runs model-written Python inside a Docker container with a 512 MB memory cap and one CPU, mounting the project directory read-write and leaving networking at its default. Which weakness is most serious?","options":[{"text":"512 MB may be too little memory for legitimate data work.","correct":false,"tempting":"A capacity concern that shows up as an obvious crash rather than a security weakness, and it is trivially adjusted."},{"text":"The CPU limit still lets a busy loop occupy the container for the full timeout.","correct":false,"tempting":"True, and worth capping with a wall-clock timeout, but it costs a slot rather than exposing anything."},{"text":"Networking is on by default, so generated code can reach the internet and the cloud metadata endpoint, and the writable project mount lets it modify source files.","correct":true},{"text":"The base image may contain unpatched packages.","correct":false,"tempting":"Real hygiene advice, and much less urgent than an open network path out of a container running untrusted code."}]},"prev":{"slug":"data-boundaries-and-redaction","title":"What leaves the building","url":"https://addaly.com/learn/agents-and-automation/data-boundaries-and-redaction"},"next":{"slug":"secrets-and-agent-identity","title":"The agent gets its own account","url":"https://addaly.com/learn/agents-and-automation/secrets-and-agent-identity"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Running code the model wrote', in Agents and Automation. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/agents-and-automation/sandboxing-what-it-runs (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Running code the model wrote","courseTitle":"Agents and Automation","module":{"index":5,"slug":"safety-and-blast-radius","title":"Permissions, injection and blast radius"},"answer":null,"keyIdea":"There is no in-process Python sandbox; isolate at the container or virtual-machine boundary, and turn the network off by default.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"one-tool-changes-everything","runs":[{"text":"One tool changes everything"}]},{"t":"p","runs":[{"text":"Give an agent a code-execution tool and every careful permission decision collapses into a single question, because arbitrary code can do whatever the process can do: read files, open sockets, spend money, and read the environment variables holding your other credentials."}]},{"t":"p","runs":[{"text":"This is still worth doing. Code is often the right tool: it does arithmetic properly, handles a thousand rows without them entering the context, and fails loudly. It just needs a boundary."}]},{"t":"h2","id":"the-layers-weakest-to-strongest","runs":[{"text":"The layers, weakest to strongest"}]},{"t":"p","runs":[{"text":"1. A restricted namespace in the same process.","b":true},{"text":" Handing a stripped set of builtins to an eval call. This is "},{"text":"not a sandbox","b":true},{"text":". Python’s object graph makes escapes routine: walking from any object up to its base class and back down through the list of subclasses to find something dangerous is a party trick, not research. Do not ship it, and be suspicious of any library that offers it as one."}]},{"t":"p","runs":[{"text":"2. A separate process, its own OS user, resource limits.","b":true},{"text":" A genuine improvement: a crash is contained, and address-space and CPU limits bound the damage. Still the same kernel and the same filesystem, and the process can still open sockets."}]},{"t":"p","runs":[{"text":"3. A container, with the flags that matter.","b":true}]},{"t":"code","lang":"bash","label":"Bash","text":"docker run --rm \\\n  --network none \\\n  --read-only --tmpfs /tmp:size=64m \\\n  --memory 512m --cpus 1 --pids-limit 128 \\\n  --cap-drop ALL --security-opt no-new-privileges \\\n  -v \"$PWD/work:/work\" \\\n  python:3.12-slim python /work/task.py"},{"t":"p","runs":[{"text":"--network none","code":true},{"text":" matters most, and it is the flag missing from every tutorial. "},{"text":"--pids-limit","code":true},{"text":" stops a fork bomb. "},{"text":"--read-only","code":true},{"text":" with a small tmpfs means the filesystem cannot be filled or persistently modified. "},{"text":"--cap-drop ALL","code":true},{"text":" removes capabilities that almost nothing needs."}]},{"t":"p","runs":[{"text":"4. Kernel-level isolation.","b":true},{"text":" gVisor, which puts a user-space kernel between the workload and the host, or Firecracker microVMs, which give each run its own kernel. This is what the hosted sandbox products are built on. Reach for it when you run untrusted code from many different users on shared hardware."}]},{"t":"p","runs":[{"text":"5. Somebody else’s machine.","b":true},{"text":" A hosted sandbox, so the blast radius is contractual rather than yours."}]},{"t":"h2","id":"the-three-limits-people-forget","runs":[{"text":"The three limits people forget"}]},{"t":"ul","items":[[{"text":"Wall-clock time.","b":true},{"text":" Wrap the command in a timeout. An infinite loop otherwise holds a slot forever."}],[{"text":"Output size.","b":true},{"text":" A program printing in a loop fills your disk and then your context. Cap captured output at something like 100 KB, and say in the result that it was capped."}],[{"text":"Network.","b":true},{"text":" Off unless proven necessary, then allowlisted. The default is on, and the default is wrong."}]]},{"t":"h2","id":"the-mount-that-ruins-the-day","runs":[{"text":"The mount that ruins the day"}]},{"t":"p","runs":[{"text":"Mount one working directory. Never the repository, never the home directory, and never (this appears in tutorials with alarming frequency) the container runtime’s own control socket. Access to that socket lets the container start a new privileged container mounting the host filesystem. That is not a sandbox with a hole in it; it is no sandbox."}]},{"t":"h2","id":"the-free-path","runs":[{"text":"The free path"}],"role":"free"},{"t":"p","runs":[{"text":"Docker with the flags above, on a server you already have. On Linux, "},{"text":"bubblewrap","code":true},{"text":" and "},{"text":"firejail","code":true},{"text":" are lighter and effective. A throwaway virtual machine you rebuild from a snapshot is crude and perfectly sound."}]},{"t":"p","runs":[{"text":"Paid, and worth it at volume: "},{"text":"E2B","b":true},{"text":", "},{"text":"Modal","b":true},{"text":", "},{"text":"Daytona","b":true},{"text":", and the sandbox offerings from the major cloud providers. What you buy is warm-start latency, per-user isolation and somebody else’s security team."}]},{"t":"h2","id":"the-test-that-matters","runs":[{"text":"The test that matters"}]},{"t":"p","runs":[{"text":"Write the escape yourself. Try to open a socket. Try to read the password file. Try to reach your cloud’s instance metadata address. If your sandbox lets any of those through, you have found out now rather than from a bill."}]}],"check":{"question":[{"text":"A team runs model-written Python inside a Docker container with a 512 MB memory cap and one CPU, mounting the project directory read-write and leaving networking at its default. Which weakness is most serious?"}],"options":[{"text":[{"text":"512 MB may be too little memory for legitimate data work."}],"correct":false,"tempting":[{"text":"A capacity concern that shows up as an obvious crash rather than a security weakness, and it is trivially adjusted."}]},{"text":[{"text":"The CPU limit still lets a busy loop occupy the container for the full timeout."}],"correct":false,"tempting":[{"text":"True, and worth capping with a wall-clock timeout, but it costs a slot rather than exposing anything."}]},{"text":[{"text":"Networking is on by default, so generated code can reach the internet and the cloud metadata endpoint, and the writable project mount lets it modify source files."}],"correct":true,"tempting":null},{"text":[{"text":"The base image may contain unpatched packages."}],"correct":false,"tempting":[{"text":"Real hygiene advice, and much less urgent than an open network path out of a container running untrusted code."}]}],"order":[3,0,1,2],"why":null},"prev":{"slug":"data-boundaries-and-redaction","title":"What leaves the building","minutes":9},"next":{"slug":"secrets-and-agent-identity","title":"The agent gets its own account","minutes":9}}}