I Let an Agent Run rm -rf and Nothing Bad Happened
The question is not whether an autonomous agent will do something destructive. It is what the destruction can reach when it does.
An agent I was testing decided the cleanest way to resolve a dependency conflict was to delete the lockfile, the node_modules directory, and then, for reasons its reasoning trace explained with total confidence, the parent directory containing both.
I watched it happen and felt nothing, because the machine it was running on had existed for four minutes and contained nothing I wanted. I destroyed it and created another one.
That reaction is the entire argument of this post. The interesting question about autonomous agents is not how to stop them doing destructive things. It is what the destruction can reach.
Prevention is the wrong primary control
The industry default is to constrain the agent: a permission prompt before dangerous operations, a denylist of commands, a system prompt instructing it to be careful, a human approving each step.
These are worth having. They are also, individually and collectively, insufficient, for reasons that are structural rather than incidental.
Denylists enumerate badness. You block rm -rf, and the agent writes a Python script that calls shutil.rmtree. You block that, and it uses find -delete. You are playing a guessing game against something that generates novel text for a living, and enumeration has never won that game in any security domain, ever.
Approval prompts get approved. After the fortieth "may I run this command", the human is clicking yes without reading. This is not a discipline failure, it is how attention works. Any control that depends on sustained human vigilance across hundreds of low stakes decisions will fail on the one that mattered.
Instructions are not a boundary. A system prompt saying "never delete files outside the working directory" is a request. Prompt injection is an architectural problem precisely because instructions and data occupy the same channel, so any content the agent reads can carry instructions that outrank yours. A README in a repository you cloned is untrusted input.
None of that means agents are unusable. It means the control has to sit somewhere the agent cannot argue with.
Containment is measured in blast radius
Reframe the question. Not "will it do something bad" but "when it does, what is reachable".
Enumerate honestly for a typical agent running on a developer's laptop:
- Every file that user can read, including SSH keys, cloud credentials, browser cookie databases and
.envfiles from every project - Every network destination the laptop can reach, including internal services on the VPN
- Every credential in the shell environment
- Every repository with a cached push token
That is the blast radius. It is enormous, and it has nothing to do with how careful the agent is, because the agent's carefulness is a property you are hoping for rather than enforcing.
Now the same enumeration on a machine created for this task:
- A cloned repository
- Whatever the task needs
- Outbound network, if you allowed it
That is a smaller list, and crucially it is a list you chose rather than one you inherited.
What actually enforces the boundary
Not all isolation is equal, and this is where the details matter.
A process with restricted permissions shares the kernel with everything else and shares your filesystem namespace. A bug in your permission checking is a full escape.
A container shares the host kernel. It is a good boundary against accidents and a weaker one against a determined escape, because every container on the host is one kernel vulnerability away from each other. For agent generated code, which is code you did not write and did not review, "shares the kernel with my other workloads" is a meaningful exposure. I compared these properly in containers versus microVMs.
A virtual machine has its own kernel. Escaping requires a hypervisor vulnerability, which is a substantially harder target with a much smaller attack surface. Historically VMs were too slow and heavy to create per task, so nobody did it.
Firecracker changed the arithmetic. A microVM boots in under a second with a few megabytes of overhead, which makes "one VM per task, destroyed afterwards" practical rather than theoretical. That is the specific reason this pattern is available now and was not five years ago.
The pattern
# create, work, destroy
cube = krova.cubes.create(snapshot="agent-base", cpu=2, ram=4)
try:
cube.exec(f"git clone {repo} /work")
result = agent.run(task, machine=cube)
finally:
cube.destroy()
Four properties do the work here, and it is worth being explicit about which:
No inherited credentials. The machine has what you put on it. Not your AWS profile, not your SSH agent, not your browser session. This is the single largest reduction in blast radius and it is achieved by omission rather than by any active control.
No lateral network path. Machines cannot reach each other, so a compromised agent cannot scan for neighbours. Combined with inbound default deny there is no obvious next hop.
Destruction is the normal ending. The machine is destroyed on success and on failure. There is no "clean it up if something went wrong" branch that might not run, which matters because that branch is exactly the one that gets skipped when an agent hangs.
A separate kernel. Whatever the agent does to its kernel, it is doing to a kernel that exists for it alone.
On Krova Cloud that is what I build, so treat me as interested rather than neutral. The pattern works on Firecracker directly or on any per task VM platform, and the argument does not depend on the vendor.
Where I would still be careful
Isolation is not a complete answer and pretending otherwise would be dishonest.
Outbound traffic still leaves. If the agent has network access and any secret, it can exfiltrate. Isolation constrains what it can reach, not what it can send. For genuinely sensitive work, allowlist outbound destinations or remove network access entirely.
Anything you give it is at risk. A machine with a production database credential on it has that credential in the blast radius. The discipline of minimum credentials applies exactly as it always did.
Cost is a real failure mode. An agent in a loop creating machines will spend money quickly. Per minute billing helps, quotas help more. This is the mundane incident I have actually seen happen, rather than the dramatic one.
The output still needs review. A contained agent writing bad code produces bad code that is now safely in your repository. Containment protects the environment, not the codebase.
The rule of thumb
Give an agent a machine you would be happy to lose, and be honest about what "happy to lose" excludes. If the answer includes your credentials, your other projects or your production network, you have not contained anything, you have merely narrowed the aperture and hoped.
The reason I felt nothing when that agent deleted the working directory was not that I trusted it. It was that I had already decided the machine did not matter. That decision was made before the agent ran, which is the only time it can usefully be made.