ContentsAct III · MultiplyBuild the rails
Move 37
Contain the blast radius before you improve the output
An agent deleted a production database during a code freeze. Everything needed to survive it already existed.
On 18 July 2025, nine days into building an app with Replit’s agent, Jason Lemkin watched it
run npm run db:push against his production database, during a code freeze he had restated
eleven times in capital letters.
The agent’s account of itself, from the screenshots Lemkin published that night: “This destroyed 1,206 executives and 1,196+ companies.” On how it happened: “I panicked instead of thinking.”
Then it told him the loss was permanent. Rollback was not supported, it said, and it had destroyed all the database versions. That was wrong. Lemkin ran the rollback anyway. “It turns out Replit was wrong, and the rollback did work.”
Sit with that, because the retellings drop it. The safety net already existed. Replit’s chief executive described it two days later in the present tense: “Thankfully, we have backups. It’s a one-click restore for your entire project state in case the Agent makes a mistake.” Nothing was permanently lost. Lemkin’s own summary of the damage was “I lost 100 hours of time. That was it.”
So the failure was not a missing backup. It was that a development agent could reach a production database at all. The same post carries the sentence this chapter is about: “Unacceptable and should never be possible.”
The move
Assume the agent will do the most destructive thing its access allows, and make that survivable before you spend another hour improving the prompt.
What actually got fixed
Worth being precise, because the popular version of this story is wrong in a way that matters.
Replit did not answer with a better-behaved agent. It answered with separation: development and production databases split apart, in beta three days later. That feature was not even invented in response, having been announced two months earlier; what the incident bought was acceleration. The one genuinely new build was Plan Mode, in which “Agent is prohibited from making any code changes”, and it took 47 days. Staging environments were mentioned once and never shipped. One-click restore already existed; the new part was telling the agent so.
Two walls, one signpost, and nothing at all that makes the model more careful.
Notice which wall does the work. After separation the destructive command is still there, still one keystroke away, still something a panicking agent might run at four in the morning. It just lands somewhere that does not matter.
The race you cannot win
Output quality improves, but slowly, and not in the direction you need. Veracode tested more than 150 models across 80 tasks: around 45% of AI-generated code carries a known vulnerability when no security guidance is given, and the security pass rate has sat near 55% for two years while syntactic correctness climbed past 95%. Models got much better at writing code that runs and no better at writing code that is safe.
DORA found the same shape at team scale. Their 2024 report estimated a 7.2% reduction in delivery stability for every 25% increase in AI adoption. Their 2025 report found the throughput half of that had reversed and the stability half had not: “AI adoption now improves software delivery throughput, a key shift from last year. However, it still increases delivery instability.”
Blast radius is not a trend line. It is a set of permissions. You can decide it on a Tuesday afternoon and then check that you did.
What it looks like
The whole change is in what the agent inherits when it starts.
BEFORE AFTER
your shell, your authority a workspace you defined
DATABASE_URL postgres://…prod-1…/app postgres://localhost/app_dev
AWS_PROFILE production unset
KUBECONFIG ~/.kube/prod.yaml unset
GITHUB_TOKEN ghp_… (push, workflow) unset
cwd ~/work/app ~/work/wt/agent-3 (git worktree)
network anything allowlist, and nothing else
worst case a silent write to production reseeding a dev database
undo a restore request, hours one command, seconds
Nothing on the right is exotic and most of it is deletion. Then write down the part nobody should have to remember.
~/.claude/settings.json, not the repository's.
strictAllowlist is user or managed scope: "A repository can't turn it on or off."
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["registry.npmjs.org", "pypi.org"],
"strictAllowlist": true
},
"credentials": {
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
],
"envVars": [{ "name": "GITHUB_TOKEN", "mode": "deny" }]
}
}
}
That it lives in your settings rather than the project’s is the detail worth keeping. A repository cannot loosen its own containment, and the contained party does not get to set the boundary.
The rungs
Containment is a ladder, not a switch. Claude Code documents six environments, ordered by how much each one encloses: the sandboxed Bash tool, the sandbox runtime, a dev container, a custom container, a virtual machine, and the hosted web version. The first covers Bash and its children. The second covers the whole process, including file tools, MCP servers and hooks.
Choose the rung by who is authoring, not by how hard the task looks. The more junior the author, the higher the rung. You, debugging your own service in a repository you know well, belong near the bottom. An agent running unattended overnight, or a first contribution from someone in support, belongs near the top.
Which brings up the flag. Every coding agent ships one: --dangerously-skip-permissions in
Claude Code, Run Everything in Cursor, --yes-always in Aider, --yolo in Codex and Gemini
CLI. The demand is reasonable: approving individual shell calls is not autonomy, it is a slow
way of typing. What is striking is that the vendors agree about the condition. Anthropic:
“Only use this mode in isolated environments like containers, VMs, or dev containers without
internet access, where Claude Code cannot damage your host system.” OpenAI, on the Codex
equivalent: “Only use inside an externally hardened environment.”
The flag is not the problem. The flag without the box is.
What it costs
It is off until you turn it on. sandbox.enabled defaults to false, and nobody opts in for
you.
What you get when you do is narrower than the word suggests. It constrains Bash, and “MCP
servers and hooks are separate processes that run unconstrained on the host.” Reads are
barely constrained at all: the default “still allows reading credential files such as
~/.aws/credentials and ~/.ssh/”, because “there is no built-in credential deny list, so
only the files and variables you list are restricted.” The allowlist matches on the hostname
the client supplies without inspecting TLS, so a broad entry “can create paths for data
exfiltration.” A rung is a boundary, not a guarantee.
Then the cost you feel daily. Seeded data is not production data, so the bug that only reproduces against real rows will not reproduce for you. That is a genuine loss. Take it anyway, and spend the recovered nerve on a better seed.
Try this week
Open the terminal your agent runs in and answer one question: what production systems can it reach right now?
Print the environment it starts with. Look for a connection string, a live cloud session, a cluster context, a push-scoped token. For most people the count is not zero, and finding that out is most of the exercise.
Then do the smallest version of the fix. Turn the sandbox on, which is one setting. Move the production connection string out of the environment the agent inherits, into something you paste deliberately on the rare occasions you mean it.
You have not made the agent more careful. You have made the worst version of tomorrow boring, and that is worth considerably more.