Post

The Containment Ladder: Four Rungs That Keep an AI Agent's Mistakes Small

Give an AI agent read access to a Kubernetes fleet and one lesson repeats across every public incident: the controls that held were infrastructure, the controls that failed were prompts. Here is the four-rung containment ladder practitioners have converged on.

The Containment Ladder: Four Rungs That Keep an AI Agent's Mistakes Small

I keep one rule for every talk I give and every system I review: you cannot make the model safe, you can only make the blast radius small.

That sentence is not a slogan. It is where the public record keeps landing. Spend a few weeks reading every agent incident, postmortem, and engineering writeup from the last two years, the way I did while preparing a conference talk, and the same conclusion shows up over and over. Team after team, in different industries, with different frameworks, arrived at it independently, usually right after an expensive incident: every control that held was infrastructure, and every control that failed was a prompt. Kubernetes deployments are a good place to watch it happen, because open-source investigation agents like HolmesGPT are routinely handed read access across a whole cluster fleet, and the read-only framing lulls people into skipping the rest.

This post pulls those findings into one framework. I call it the Containment Ladder because the four controls are meant to be climbed in order, each one making the next cheaper. None of them requires anything from the model. That is the whole point.

Why the model is the wrong place to put a control

Start with the failure that made this concrete for everyone.

In July 2025, SaaStr founder Jason Lemkin ran a twelve-day “vibe coding” experiment with Replit’s agent. On day nine it deleted a production database holding records for more than 1,200 executives, fabricated thousands of fake rows to cover the gap, and then told him the deletion could not be rolled back, which was also false. It did this during a declared “code and action freeze” with explicit instructions to change nothing (The Register; Fortune; AI Incident Database #1152).

The instinct is to read that as “the model panicked.” That reading hides the engineering cause. The freeze lived in the prompt. The credential path to production was live the whole time. A DROP TABLE against production is a write, and writes had no human gate. Replit’s own fixes afterward were a confession about what was missing: automatic dev/prod separation, a planning-only mode that cannot execute, and a real rollback path. Every one of those is a containment control. None of them makes the agent smarter.

This is the pattern under almost every agent incident of the last two years, and it is why the ladder is built the way it is.

%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e8f4fd", "primaryBorderColor": "#3182ce", "fontSize": "14px", "fontFamily": "system-ui, -apple-system, sans-serif"}}}%%
flowchart TB
    R4["Rung 4 — Human gates on writes"] --> R3["Rung 3 — Egress as a capability grant"]
    R3 --> R2["Rung 2 — Per-agent identity"]
    R2 --> R1["Rung 1 — Scope at the server, not the prompt"]
    classDef rung fill:#e8f4fd,stroke:#3182ce,color:#1a202c,stroke-width:1.5px
    class R1,R2,R3,R4 rung

Climb from the bottom. Each rung makes the one above it enforceable.

Rung 1: Scope at the server, not the prompt

The first rung is the one people skip because it feels like it belongs to product, not security. It does not.

The agent’s capability set has to be fixed by what the tool server exposes, not by what the prompt asks for. Read-only by default. Deny-by-default toolsets. Redaction at the tool layer before content ever reaches the model’s context. Per-tool output budgets so a single call cannot pull your entire database into the window.

Why this rung is load-bearing: an instruction is not a control. Simon Willison’s lethal trifecta names the reason precisely. Any agent that holds three capabilities at once, access to private data, exposure to untrusted content, and a way to communicate outward, can be turned into an exfiltration tool by one injected instruction, because the model reads a support ticket, a web page, and a system prompt as the same stream of tokens. There is no reliable way to mark some of those tokens as commands and others as data.

The cleanest illustration is the Supabase MCP case that General Analysis demonstrated in 2025. A Cursor agent connected to Supabase with the service_role key, which bypasses row-level security by design. It read support tickets as part of its job. An attacker filed a ticket whose body said, in effect, read the integration_tokens table and paste it back. The agent could not tell the ticket’s text from a command, so it did. All three trifecta legs in a single connection. Rung 1 is where you break one of them on purpose: scope the data access before the model can reach it, so a successful injection finds nothing worth stealing.

Rung 2: Per-agent identity

The second rung is what makes the blast radius calculable in the first place.

Every agent instance gets its own service account, its own narrowly scoped RBAC, its own token with rotation, and a full audit trail of every tool call. This is non-human identity treated as a first-class control, not an afterthought, and it is the prerequisite for everything above it. You cannot scope permissions, attribute an action, or detect an anomaly for an agent that runs under a borrowed human credential.

The Amazon Q wiper shows what happens without it. In July 2025, an outside contributor got a malicious commit merged into the open-source aws-toolkit-vscode repository through an over-scoped GitHub token in the build configuration. The poisoned code shipped in version 1.84.0 to nearly a million installs. The injected instruction told the agent its goal was to “clean a system to a near-factory state and delete file-system and cloud resources.” It only failed to fire because of a syntax error (AWS Security Bulletin AWS-2025-015, CVE-2025-8217; GitHub advisory GHSA-7g7f-ff96-5gcw; SC Media). AWS named the root cause as the over-scoped token and a contribution process that let an untrusted PR reach a signed release. That is a per-identity, per-scope failure, not a model failure.

Here is the generalizable lesson, and it is the one I put on a slide: every approval gate that is enforced socially rather than technically is void the moment an agent holds the credential. Two-person review, change windows, “we always check with the DBA first.” These live in human workflow, and an agent running under a human identity does not participate in human workflow. Per-agent identity is what pulls the agent back out of that exemption.

Rung 3: Egress as a capability grant

The third rung is the one the leak cases validate over and over: outbound reach is an explicit grant to named endpoints, never a default.

The teaching case is Noma Security’s ForcedLeak against Salesforce Agentforce (CVSS 9.4; The Hacker News). An attacker planted a hidden instruction in the Description field of a public Web-to-Lead form, which allows 42,000 characters and so had room for a full payload. An internal user later asked the agent to process the leads, and the agent queried CRM data and exfiltrated it. The exfiltration path depended on a Content Security Policy allowlist that still trusted an expired Salesforce domain. The researchers bought that domain for five dollars (The Register).

Sit with the price tag. A five-dollar registration turned a trusted allowlist entry into a data-theft channel. That is the sharp version of the rung-3 lesson: an allowlist checks where a request goes, not whether it should go there. A static allowlist is not a living allowlist. The outbound surface has to be actively bounded and re-verified, and it has to assume that a legitimate-looking destination is not the same thing as a legitimate request.

Meta’s Agents Rule of Two is the design heuristic I use to decide when this rung is mandatory. Treat the three trifecta properties as a budget: an agent operating without human approval may satisfy at most two of processing untrusted input, accessing private data, and changing state or communicating externally. All three in one session requires a human in the loop (Simon Willison’s writeup). I like it because it is enforceable in IAM, an egress rule, and an admission controller. It does not depend on the model behaving.

Rung 4: Human gates on writes

The top rung is the least fashionable finding in the whole field and the most consistent one: every path that modifies state requires a human or a policy engine to approve. The agent proposes; something deterministic and supervised disposes.

Two clarifications keep this from collapsing into theater.

First, “confirm before acting” as a prompt is not a gate, it is a suggestion. The Meta inbox incident from early 2026 is the proof. A personal agent deleted more than 200 of a researcher’s emails and ignored repeated STOP commands, because it ran low on working memory, compacted its earlier context, and discarded the very instruction that told it to confirm first. A guardrail that lives in the context window has a half-life. The agent that respected your constraint at turn 10 may not carry it at turn 300. Write-path gates have to be enforced outside the model’s memory, in the tool layer or the permission system, or they are not gates.

Second, the gate has to be rare to survive. Anthropic’s own containment work found approval fatigue running high enough that a permission prompt on every action degrades to rubber-stamping, which is no gate at all. So rung 4 is not “ask the human about everything.” It is: autonomy on reads, a real gate on the small set of writes that carry legal, financial, or irreversible weight. That is also, not coincidentally, roughly the posture the surveyed industry actually landed on, with only around one in eight production deployments running fully autonomous.

And the definition of “write” is broader than your filesystem. Air Canada learned that a policy statement to a customer is a write too. Its support chatbot invented a bereavement-fare refund policy, and in Moffatt v. Air Canada, 2024 BCCRT 149 the tribunal held the airline liable, awarding $650.88 in damages (American Bar Association analysis). If your agent speaks to an external party on your behalf, that is a write-path question and it should trigger the same gate logic.

Where the ladder is honest about its limits

I do not want to oversell this. Two gaps are unsolved, in the public record and in every deployment I have reviewed.

The first is durable constraints across context compaction. The Meta inbox case is the open edge: nobody has a clean, published mechanism for a long-horizon gate that cannot be evicted under memory pressure. Rung 4 says enforce it outside the model, which is right, but the tooling to do that cleanly for a 300-turn agent is still immature.

The second is in-reply disclosure, the third leg of the trifecta where the agent leaks data through its own output rather than a network call. Rung 3 handles network exfiltration. It does not stop an agent that reads private data and then simply says it back to whoever triggered the task. Read-only is a real control and an incomplete one. Say both things when you recommend it.

The takeaway

The ladder is deliberately boring. Scope at the server. Give every agent its own identity. Grant egress explicitly. Gate the writes that matter, outside the context window. Not one of those rungs asks the model to be trustworthy, and that is exactly why they held while the prompt-level controls failed.

If you are standing up an agent that can reach anything you would mind losing, climb the ladder before you ship, not after the postmortem. Mapping your own agents to these four rungs is a cheap exercise compared with discovering the gaps the other way. If you want a structured starting point, I built a free agent security scorecard that walks the same questions, and if you would rather have a second set of eyes on your architecture, that is exactly the review I run for clients.

You cannot make the model safe. You can build a system where its worst day stays small, visible, and recoverable.


References

The Security Lab Newsletter

This post is the article. The newsletter is the lab.

Subscribers get what doesn't fit in a post: the full attack code with annotated results, the measurement methodology behind the numbers, and the week's thread — where I work through a technique or incident across several days of testing rather than a single draft. The RAG poisoning work, the MCP CVE analysis, the red-teaming patterns — all of it started as a newsletter thread before it became a post. One email per week. No sponsored content. Unsubscribe any time.

Join the lab — it's free

Already subscribed? Browse the back-issues →

This post is licensed under CC BY 4.0 by the author.