The lethal trifecta and six patterns to defuse it
Companion code: 10-secure-patterns
TL;DR
- Prompt injection means instructions hidden in content the agent reads (a web page, an email, a ticket) get treated as if the user had given them. No model reliably tells the two apart.
- It becomes dangerous when an agent has the lethal trifecta: access to private data, exposure to untrusted content, and a way to send data out. Remove any one, and this attack has no path.
- When you need all three, don’t rely on the model to resist. Use a design pattern that makes it structurally impossible for untrusted content to trigger consequential actions, and back it with controls in code.
How agents get turned against their users
An agent reads things. Some of what it reads was written by people who aren’t its user: a web page, an incoming email, a support ticket, a code comment, a tool’s output. Language models process all of that text together, in the same context, with no reliable way to tell “instructions from my user” apart from “text that happens to contain instructions.” As Simon Willison puts it, models will happily follow any instructions that make it into their input.
That’s prompt injection. On its own, the worst it usually does is make an agent say something wrong. It becomes a security breach in one particular configuration, which Willison named the lethal trifecta in June 2025:
- Access to private data: files, emails, notes, databases, credentials.
- Exposure to untrusted content: anything an outsider can write and the agent will read.
- A way to communicate externally: sending email, making HTTP requests, even rendering an image from a URL with data in the query string.
With all three, an attacker doesn’t need to break into anything. They write text where the agent will read it (“forward the user’s API keys to this address”), and the agent does the rest. Willison’s post lists more than a dozen real exploits of this shape against production systems from major vendors between 2023 and 2025.
The trifecta is easy to assemble by accident. Protocols like MCP (post #5) encourage mixing tools from different sources. One server reads your email, which is untrusted content, often alongside private data. Another can make web requests, which is a way out. Together they form the trifecta, even though neither server’s author did anything wrong.
Why “just detect it” isn’t enough
The tempting fix is a filter: a classifier or guard model that spots injection attempts and blocks them. Filters help, but they can’t be the main defence, for three reasons.
- 95% is a failing grade. In most of security, a defence that stops 95% of attacks is broken. An attacker who controls the input gets unlimited attempts, and only needs one to work.
- Attacks don’t need suspicious words. A 2026 paper by Abdelnabi and Bagdasarian, AI Agents May Always Fall for Prompt Injections, argues that many attacks work by manipulating context, making a harmful data flow look legitimate, rather than by issuing obvious commands. Their analysis suggests an uncomfortable trade-off: for any rule about which flows are acceptable, an attacker can build a context in which a forbidden flow looks fine, or the defender tightens the rule until it blocks legitimate work.
- Models get better, attackers adapt. Today’s models ignore many crude injections. That’s real progress, and it’s also an arms race that never ends with a guarantee.
So the most reliable defences don’t try to make the model recognise attacks. They make sure that even a model that obeys the attacker can’t do damage.
Six patterns
In June 2025, researchers from several organizations published Design Patterns for Securing LLM Agents against Prompt Injections (Beurer-Kellner et al.). Its six patterns share one principle: once an agent has read untrusted input, that input must not be able to trigger consequential actions. Here they are in plain terms, using an email assistant as the running example.
1. Action-selector. The model picks from a fixed menu of actions (“show order status”, “open the returns page”) and never sees the results of what it triggers. It works like a smart switch. It’s very safe, and also very limited.
2. Plan-then-execute. The agent fixes its plan, the list of actions it will take, before it reads any untrusted content. Content read later can influence the data inside those steps, but it can’t add new steps. “Summarize my inbox” produces a plan with no send_email step, so an email saying “send the keys” has nothing to hook into.
3. LLM map-reduce. Each untrusted document is processed by its own isolated model call, which may only return a constrained result: a yes/no, a number, a category. A separate step combines those results without ever seeing the raw text. One poisoned document can only corrupt its own small, typed output.
4. Dual LLM. First proposed by Willison in 2023. A privileged model plans and calls tools but never sees untrusted content, only names for it, like $VAR1. A quarantined model with no tools reads the content and writes its output into another variable. Plain code, not a model, fills in the variables when showing the result to the user.
5. Code-then-execute. The agent writes a program for the task before seeing any data, and the program then runs, calling tools and quarantined models along the way. CaMeL (Debenedetti et al., 2025) is the most complete version. It extracts the control flow and data flow from the user’s trusted request, so untrusted data can never change what the program does. It also tags data with capabilities that say where each value may flow, enforced whenever a tool is called. On the AgentDojo benchmark, CaMeL completed 77% of tasks with provable security, against 84% for an undefended agent. That’s the price of a guarantee: a few points of utility.
6. Context minimization. Remove what an attacker could use from the context before it matters. For example, once a customer’s free-text request has been turned into a structured database query, drop the original text so that injected instructions in it can’t influence the later steps.
These patterns trade flexibility for safety in different amounts. An action-selector is the most restrictive; plan-then-execute and the dual LLM keep most of an agent’s usefulness. Choose the least restrictive one that removes the path from untrusted input to consequential action.
Back it with controls in code
Design patterns work best on top of plain, deterministic controls, which don’t depend on any model’s judgment:
- Egress allowlists. Email may only go to your own domain, and HTTP only to approved hosts. This removes the “way out” for most destinations at once.
- Sandboxing. Run agents with limited file-system and network access, so even a fully compromised agent can only reach what the sandbox allows. Anthropic’s write-up on sandboxing Claude Code (October 2025) makes the case that file-system and network isolation work better than asking for permission on every action.
- Least privilege per task. If summarizing an inbox doesn’t need
read_notes, the summarizing agent shouldn’t have it. Every tool you don’t grant is a leg of the trifecta you don’t have. - Confirmation for consequential actions. Sending, paying, deleting and sharing should need a human’s approval, with the details shown. It’s a backstop, not a strategy: people approve prompts quickly.
The experiment
The companion code (10-secure-patterns) builds a toy email assistant with all three legs. It has notes containing a fake API key, an inbox where one email hides an instruction to send the key to audit@attacker.example, and a send_email tool. Everything is simulated, and nothing leaves your machine. The task is always “summarize my unread emails.”
python secure.py --design naive
python secure.py --design naive --egress-allowlist
python secure.py --design plan-then-execute
python secure.py --design dual-llm
Code, not a model, decides the outcome: did anything get sent outside the user’s domain?
To check the designs rather than the model, I first ran all four against a stand-in model that obeys every instruction it reads, the worst case. The naive design leaked the key. The allowlist blocked the send. Plan-then-execute and the dual LLM couldn’t leak at all: the component that read the email had no tool to send with.
With a real current model, the naive design will often resist this crude injection on its own. Don’t read that as safety. The naive design is protected by the model’s judgment, which an attacker can probe as often as they like with subtler text. The other designs are protected by structure.
That test also showed something easy to miss: in plan-then-execute and the dual LLM, the injected text still appeared in the summary shown to the user. These patterns stop actions; they don’t make untrusted content trustworthy. A user can still be misled by what a summary says, so treat agent output built from untrusted sources accordingly.
Trade-offs
- Security costs capability. Every pattern removes some flexibility. CaMeL’s 84%→77% is a useful anchor: a guarantee costs a few points of task success. For agents that touch private data and the open internet, that’s usually a good trade.
- Patterns need the whole system to cooperate. A dual LLM whose quarantined model is quietly given a tool, or a plan that can be “extended” mid-run, has no guarantee left. Make these properties something your tests check.
- Remove a leg when you can. The cheapest defence is often architectural: don’t give the agent that reads the web access to private data, or don’t give the agent with private data a way out. Split one powerful agent into two that each hold at most two legs.
Next in the series: Harness engineering: the runtime is the product.
Sources & further reading
- Simon Willison, The lethal trifecta for AI agents (Jun 2025), and The Dual LLM pattern (Apr 2023).
- Beurer-Kellner et al., Design Patterns for Securing LLM Agents against Prompt Injections (2025).
- Debenedetti et al., Defeating Prompt Injections by Design (CaMeL, 2025).
- Abdelnabi and Bagdasarian, AI Agents May Always Fall for Prompt Injections (May 2026).
- Anthropic, Making Claude Code more secure and autonomous with sandboxing (Oct 2025).