{"course":{"slug":"how-software-changed","title":"How Software Development Changed","url":"https://addaly.com/learn/how-software-changed","level":"intermediate","track":"work"},"lesson":{"slug":"agents-and-blast-radius","title":"Confining an agent","url":"https://addaly.com/learn/how-software-changed/agents-and-blast-radius","position":42,"of":87,"minutes":10,"answer":"An unattended agent inherits everything you can read and run, and text it reads can act as instruction.","keyIdea":"An unattended agent inherits everything you can read and run, and text it reads can act as instruction, so confinement must be a real boundary (a container without your credentials) with a permission list narrow enough that a prompt still means something."},"body":{"format":"markdown","text":"## What you are actually granting\n\nA tool that can run commands and edit files unattended has, by default, whatever you have. Read the sentence slowly, because the implications are usually not thought through at the moment somebody clicks *allow*.\n\nIt can read every file your user can read, which includes `~/.aws/credentials`, `~/.ssh/id_ed25519`, `.env` files in every project you have ever cloned, and your browser's cookie store. It can run any command, which includes `rm`, `git push --force`, `terraform apply`, `psql` against whatever `DATABASE_URL` points at, and `curl` to anywhere. It can install packages, which executes code from the internet. And it will do all of this from an instruction it interpreted, which may have come from a file it read.\n\nThat last point matters and is easy to miss. If the agent reads an issue, a README, a dependency's documentation or a web page, text inside that content can look like an instruction. This is prompt injection, it is unsolved, and it means the boundary you need is a real one (a sandbox) rather than a promise about behaviour.\n\n> Figure: Three levels of confinement. Layers, top to bottom: Level 1, a clean tree and a diff you read (Commit first, let it work, read the diff, accept it or throw it away in one keystroke. Protects the repository and nothing else.); Level 2, a container holding the project and nothing else (No home directory, no SSH keys, no cloud credentials, because they are not in the filesystem it has. Twenty minutes to set up once, and where most people should be.); Level 3, a disposable machine with no network but a registry (For running code you have not read at all, or for anything touching a system you cannot afford to have touched.). Text an agent reads can act as an instruction, so the boundary has to be a real one rather than a promise about behaviour. Whatever the level, a human still reads the diff.\n\n## The three levels of confinement\n\n**Level 1: a clean tree and a diff you read.** The minimum. Commit first, let it work, read `git diff`, accept or `git checkout .`. This protects the repository and nothing else.\n\n**Level 2: a container or dev container.** The agent runs inside a container with the project mounted and nothing else. It cannot see your home directory, your SSH keys or your cloud credentials, because they are not in the filesystem it has.\n\n```json\n// .devcontainer/devcontainer.json\n{\n  \"image\": \"mcr.microsoft.com/devcontainers/python:3.12\",\n  \"mounts\": [],\n  \"remoteEnv\": { \"AWS_PROFILE\": \"\", \"GITHUB_TOKEN\": \"\" },\n  \"postCreateCommand\": \"pip install -r requirements.txt\"\n}\n```\n\nDocker and Podman are free; Podman runs rootless, which is a further layer. This level takes twenty minutes to set up once and is where most people should be.\n\n**Level 3: a disposable VM or a remote sandbox, with no network except a package registry.** For running code you have not read at all, or for anything touching a system you cannot afford to have touched.\n\n## Credentials: the part people get wrong\n\nSandboxing the filesystem is pointless if the environment carries a token that can deploy.\n\n- **Separate identities.** A git identity for agent commits, with push rights to branches and none to `main`. When something odd appears in the history you know immediately which it was.\n- **Read-only database credentials by default.** A separate role, `GRANT SELECT` only, pointed at a restored snapshot rather than production. Most exploration needs nothing more.\n- **No cloud credentials in the environment at all** unless the specific task needs them, and then scoped and short-lived.\n- **Never `.env` in the working directory** during an agent session. Move it, or use a secret manager that requires an explicit fetch.\n\nThe test to apply: **if this session did the worst thing it could do, what would that be?** If the answer involves production data or a deployment, the confinement is not yet right.\n\n## Allow and deny lists\n\nMost agentic tools support pre-approving some commands and blocking others. Configure it, because the default of approving each prompt individually degrades into approving everything within an hour: the prompts arrive faster than judgement can be applied to them.\n\nA sound starting point:\n\n- **Auto-allow**: `ls`, `cat`, `rg`, `git status`, `git diff`, `git log`, the test command, the type checker, the linter, the formatter.\n- **Always ask**: any `git push`, any package install, any network call, any file write outside the project, anything with `sudo`.\n- **Always deny**: `rm -rf`, `git push --force`, anything touching `~/.ssh` or `~/.aws`, anything naming a production host.\n\nThe principle is that the read-only operations, which are the vast majority, should be silent, so that a prompt actually means something when it appears. A permission dialogue that fires forty times an hour is not a control; it is a training exercise in clicking yes.\n\n## Read the diff, always\n\nWhatever the confinement, the last step never changes: the diff is read by a human before it goes anywhere.\n\nThe sandbox stops a mistake from destroying anything. It does nothing about a plausible, wrong change landing in your repository, which is the failure this whole course is about, and the only defence is the reading.\n\nOne practical note. The tools, their names, their permission models and their sandboxing stories change every few months, and anything written about specific products here would be wrong by next year. The three questions do not change: what can it read, what can it run, and who sees the diff before it lands."},"check":{"question":"A team runs agents inside containers with no credentials mounted, but auto-approves every command so work is not interrupted. What risk does the sandbox not address?","options":[{"text":"Package installation, since containers still reach the network and a malicious dependency executes inside the sandbox.","correct":false,"tempting":"Real, but the container is exactly what limits the damage of that execution; the uncontained risk is what leaves the container as a commit."},{"text":"A plausible but wrong change being committed and merged, which the container does nothing to prevent.","correct":true},{"text":"Resource exhaustion, because an unattended agent can spawn processes until the host becomes unusable.","correct":false,"tempting":"A genuine nuisance, and one containers can cap with limits; it is an availability problem rather than a correctness one."},{"text":"Prompt injection from files the agent reads, since instructions embedded in content bypass container isolation entirely.","correct":false,"tempting":"Injection does bypass the sandbox's intent, but the container still bounds what the injected instruction can reach: the unbounded path is the diff."}]},"prev":{"slug":"git-as-a-safety-net","title":"Git is what makes speed safe","url":"https://addaly.com/learn/how-software-changed/git-as-a-safety-net"},"next":{"slug":"debugging-did-not-change","title":"Debugging still works the old way","url":"https://addaly.com/learn/how-software-changed/debugging-did-not-change"},"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) 'Confining an agent', in How Software Development Changed. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/how-software-changed/agents-and-blast-radius (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":"Confining an agent","courseTitle":"How Software Development Changed","module":{"index":5,"slug":"the-working-loop","title":"The working loop"},"answer":null,"keyIdea":"An unattended agent inherits everything you can read and run, and text it reads can act as instruction, so confinement must be a real boundary (a container without your credentials) with a permission list narrow enough that a prompt still means something.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"stack","title":"Three levels of confinement","layers":[{"label":"Level 1, a clean tree and a diff you read","detail":"Commit first, let it work, read the diff, accept it or throw it away in one keystroke. Protects the repository and nothing else."},{"label":"Level 2, a container holding the project and nothing else","detail":"No home directory, no SSH keys, no cloud credentials, because they are not in the filesystem it has. Twenty minutes to set up once, and where most people should be."},{"label":"Level 3, a disposable machine with no network but a registry","detail":"For running code you have not read at all, or for anything touching a system you cannot afford to have touched."}],"caption":"Text an agent reads can act as an instruction, so the boundary has to be a real one rather than a promise about behaviour. Whatever the level, a human still reads the diff."},"alt":"Figure: Three levels of confinement. Layers, top to bottom: Level 1, a clean tree and a diff you read (Commit first, let it work, read the diff, accept it or throw it away in one keystroke. Protects the repository and nothing else.); Level 2, a container holding the project and nothing else (No home directory, no SSH keys, no cloud credentials, because they are not in the filesystem it has. Twenty minutes to set up once, and where most people should be.); Level 3, a disposable machine with no network but a registry (For running code you have not read at all, or for anything touching a system you cannot afford to have touched.). Text an agent reads can act as an instruction, so the boundary has to be a real one rather than a promise about behaviour. Whatever the level, a human still reads the diff."}},"blocks":[{"t":"h2","id":"what-you-are-actually-granting","runs":[{"text":"What you are actually granting"}]},{"t":"p","runs":[{"text":"A tool that can run commands and edit files unattended has, by default, whatever you have. Read the sentence slowly, because the implications are usually not thought through at the moment somebody clicks "},{"text":"allow","i":true},{"text":"."}]},{"t":"p","runs":[{"text":"It can read every file your user can read, which includes "},{"text":"~/.aws/credentials","code":true},{"text":", "},{"text":"~/.ssh/id_ed25519","code":true},{"text":", "},{"text":".env","code":true},{"text":" files in every project you have ever cloned, and your browser’s cookie store. It can run any command, which includes "},{"text":"rm","code":true},{"text":", "},{"text":"git push --force","code":true},{"text":", "},{"text":"terraform apply","code":true},{"text":", "},{"text":"psql","code":true},{"text":" against whatever "},{"text":"DATABASE_URL","code":true},{"text":" points at, and "},{"text":"curl","code":true},{"text":" to anywhere. It can install packages, which executes code from the internet. And it will do all of this from an instruction it interpreted, which may have come from a file it read."}]},{"t":"p","runs":[{"text":"That last point matters and is easy to miss. If the agent reads an issue, a README, a dependency’s documentation or a web page, text inside that content can look like an instruction. This is prompt injection, it is unsolved, and it means the boundary you need is a real one (a sandbox) rather than a promise about behaviour."}]},{"t":"h2","id":"the-three-levels-of-confinement","runs":[{"text":"The three levels of confinement"}]},{"t":"p","runs":[{"text":"Level 1: a clean tree and a diff you read.","b":true},{"text":" The minimum. Commit first, let it work, read "},{"text":"git diff","code":true},{"text":", accept or "},{"text":"git checkout .","code":true},{"text":". This protects the repository and nothing else."}]},{"t":"p","runs":[{"text":"Level 2: a container or dev container.","b":true},{"text":" The agent runs inside a container with the project mounted and nothing else. It cannot see your home directory, your SSH keys or your cloud credentials, because they are not in the filesystem it has."}]},{"t":"code","lang":"json","label":"JSON","text":"// .devcontainer/devcontainer.json\n{\n  \"image\": \"mcr.microsoft.com/devcontainers/python:3.12\",\n  \"mounts\": [],\n  \"remoteEnv\": { \"AWS_PROFILE\": \"\", \"GITHUB_TOKEN\": \"\" },\n  \"postCreateCommand\": \"pip install -r requirements.txt\"\n}"},{"t":"p","runs":[{"text":"Docker and Podman are free; Podman runs rootless, which is a further layer. This level takes twenty minutes to set up once and is where most people should be."}]},{"t":"p","runs":[{"text":"Level 3: a disposable VM or a remote sandbox, with no network except a package registry.","b":true},{"text":" For running code you have not read at all, or for anything touching a system you cannot afford to have touched."}]},{"t":"h2","id":"credentials-the-part-people-get-wrong","runs":[{"text":"Credentials: the part people get wrong"}]},{"t":"p","runs":[{"text":"Sandboxing the filesystem is pointless if the environment carries a token that can deploy."}]},{"t":"ul","items":[[{"text":"Separate identities.","b":true},{"text":" A git identity for agent commits, with push rights to branches and none to "},{"text":"main","code":true},{"text":". When something odd appears in the history you know immediately which it was."}],[{"text":"Read-only database credentials by default.","b":true},{"text":" A separate role, "},{"text":"GRANT SELECT","code":true},{"text":" only, pointed at a restored snapshot rather than production. Most exploration needs nothing more."}],[{"text":"No cloud credentials in the environment at all","b":true},{"text":" unless the specific task needs them, and then scoped and short-lived."}],[{"text":"Never ","b":true},{"text":".env","b":true,"code":true},{"text":" in the working directory","b":true},{"text":" during an agent session. Move it, or use a secret manager that requires an explicit fetch."}]]},{"t":"p","runs":[{"text":"The test to apply: "},{"text":"if this session did the worst thing it could do, what would that be?","b":true},{"text":" If the answer involves production data or a deployment, the confinement is not yet right."}]},{"t":"h2","id":"allow-and-deny-lists","runs":[{"text":"Allow and deny lists"}]},{"t":"p","runs":[{"text":"Most agentic tools support pre-approving some commands and blocking others. Configure it, because the default of approving each prompt individually degrades into approving everything within an hour: the prompts arrive faster than judgement can be applied to them."}]},{"t":"p","runs":[{"text":"A sound starting point:"}]},{"t":"ul","items":[[{"text":"Auto-allow","b":true},{"text":": "},{"text":"ls","code":true},{"text":", "},{"text":"cat","code":true},{"text":", "},{"text":"rg","code":true},{"text":", "},{"text":"git status","code":true},{"text":", "},{"text":"git diff","code":true},{"text":", "},{"text":"git log","code":true},{"text":", the test command, the type checker, the linter, the formatter."}],[{"text":"Always ask","b":true},{"text":": any "},{"text":"git push","code":true},{"text":", any package install, any network call, any file write outside the project, anything with "},{"text":"sudo","code":true},{"text":"."}],[{"text":"Always deny","b":true},{"text":": "},{"text":"rm -rf","code":true},{"text":", "},{"text":"git push --force","code":true},{"text":", anything touching "},{"text":"~/.ssh","code":true},{"text":" or "},{"text":"~/.aws","code":true},{"text":", anything naming a production host."}]]},{"t":"p","runs":[{"text":"The principle is that the read-only operations, which are the vast majority, should be silent, so that a prompt actually means something when it appears. A permission dialogue that fires forty times an hour is not a control; it is a training exercise in clicking yes."}]},{"t":"h2","id":"read-the-diff-always","runs":[{"text":"Read the diff, always"}]},{"t":"p","runs":[{"text":"Whatever the confinement, the last step never changes: the diff is read by a human before it goes anywhere."}]},{"t":"p","runs":[{"text":"The sandbox stops a mistake from destroying anything. It does nothing about a plausible, wrong change landing in your repository, which is the failure this whole course is about, and the only defence is the reading."}]},{"t":"p","runs":[{"text":"One practical note. The tools, their names, their permission models and their sandboxing stories change every few months, and anything written about specific products here would be wrong by next year. The three questions do not change: what can it read, what can it run, and who sees the diff before it lands."}]}],"check":{"question":[{"text":"A team runs agents inside containers with no credentials mounted, but auto-approves every command so work is not interrupted. What risk does the sandbox not address?"}],"options":[{"text":[{"text":"Package installation, since containers still reach the network and a malicious dependency executes inside the sandbox."}],"correct":false,"tempting":[{"text":"Real, but the container is exactly what limits the damage of that execution; the uncontained risk is what leaves the container as a commit."}]},{"text":[{"text":"A plausible but wrong change being committed and merged, which the container does nothing to prevent."}],"correct":true,"tempting":null},{"text":[{"text":"Resource exhaustion, because an unattended agent can spawn processes until the host becomes unusable."}],"correct":false,"tempting":[{"text":"A genuine nuisance, and one containers can cap with limits; it is an availability problem rather than a correctness one."}]},{"text":[{"text":"Prompt injection from files the agent reads, since instructions embedded in content bypass container isolation entirely."}],"correct":false,"tempting":[{"text":"Injection does bypass the sandbox’s intent, but the container still bounds what the injected instruction can reach: the unbounded path is the diff."}]}],"order":[2,0,3,1],"why":null},"prev":{"slug":"git-as-a-safety-net","title":"Git is what makes speed safe","minutes":9},"next":{"slug":"debugging-did-not-change","title":"Debugging still works the old way","minutes":9}}}