Prompt Injection and the Lethal Trifecta: Securing AI Agents
Prompt injection and the lethal trifecta: why AI agents can't tell your instructions from an attacker's, how EchoLeak and the GitHub MCP attack worked.
This article is published in English.
Sources: Prompt Injection and Agent Security: The Unsolved Problem
In June 2025, researchers disclosed a flaw in Microsoft 365 Copilot and called it EchoLeak. The attack went like this. Someone sends you a normal-looking email. You don't open it, click anything or even read it. Copilot reads it anyway, because Copilot reads your whole inbox to answer your questions. Hidden in that email are instructions telling Copilot to collect sensitive data from your other documents and quietly send it to an outside server through an image that loads automatically. Zero clicks, and the victim does nothing wrong. The agent obeys, just not the right person.
EchoLeak received a CVE (CVE-2025-32711), got patched and went down as the first known case of prompt injection used for real data theft in a production AI system. It won't be the last. In the following year the same class of attack showed up in GitHub's MCP integration, GitLab's Duo, Slack's AI, Google's NotebookLM and every AI browser that launched. By late 2025, OpenAI's own security leadership was saying publicly that prompt injection might never be fully solved for browser agents, and the UK's National Cyber Security Centre agreed.
The core security fact of agentic AI is simple and we still lack a clean answer to it: the model receives your instructions and an attacker's instructions through exactly the same channel, and it can't reliably distinguish them. Below: why that's true, why it hurts agents far more than chatbots, and which defenses hold up, as opposed to the ones in vendor slides.
One channel for everything
The mechanism comes first, because misreading it is where weak security guidance starts.
A language model takes a single input: one long run of tokens. Your system prompt is part of it. The user's question is tokens. So is the web page the agent just fetched, the email it read, the tool output it got back, the parsed PDF and the alt text on an image. It all arrives as one flat sequence, and the model predicts what comes next. Nothing in there marks some tokens as trusted commands from the operator and others as untrusted data to look at. Simon Willison, who coined the term prompt injection in 2022, describes it this way: models will happily follow any instructions that reach them, whether they came from the operator or from somewhere else.
This is the heart of it, and it runs deep. No upgrade to the model will patch it, since the weakness is built into the design. These systems were made to obey instructions phrased in plain language. Then we hand them plain-language text scraped from the open internet and act shocked when the embedded orders get carried out. Injection is simply instruction-following doing its job for the wrong boss.
Compare SQL injection, which gave the attack its name. SQL injection has a real fix in parameterized queries, where the database is told unambiguously that this part is a command and this part is data, never to be treated as a command. Three decades of web security depend on drawing that line inside the machine. Inside an LLM, nobody knows how. Everything is in-band, and the command channel and data channel are one. That's why the common reflex, telling the model in the system prompt to ignore instructions found in web content, fails. You're using the compromised channel to defend the compromised channel, and a well-crafted piece of content can always argue past an English-language warning.
Two attack types often get mixed up. In direct injection, the user themself types something hostile, such as a demand to reveal the system prompt. That's closer to a jailbreak: a person attacking an agent they're permitted to use. Indirect injection is the one that threatens agents. The hostile instruction hides in outside material the agent processes during its work, like a web page, an email, a calendar invite, a code comment or a GitHub issue. The attacker never addresses the agent. They leave a trap in content they expect it to read someday on behalf of a victim who trusts it. All the incidents above were of this kind. It scales because the attacker needs no access to you, just some influence over whatever you'll aim your agent at.
The lethal trifecta
The most useful mental model here is Simon Willison's "lethal trifecta," which explains why some agents are disastrously exploitable and others merely irritating to attack.
An agent turns into a data-theft weapon when it combines three capabilities.
- Access to private data. It can read your email, private repositories, customer records or internal wiki. That's usually the reason the agent exists, so you can't just remove it.
- Exposure to untrusted content. It processes text it didn't write and you haven't vetted: web pages, incoming mail, documents, outputs from third-party systems. Again, usually the point.
- A way to communicate externally. It can send data somewhere an attacker can see it, by making an HTTP request, sending an email, calling an API, rendering an image from an attacker's URL or opening a pull request.
Any one of these is fine, and any two are usually survivable. All three in one agent under one set of credentials is a loaded gun pointed at your data. An attacker who controls the second capability can hijack the agent's instructions, use the first to gather secrets and use the third to send them out. That's the exact sequence EchoLeak and the GitHub MCP attack followed.
The GitHub case is clean enough to walk through. Invariant Labs demonstrated it in May 2025. A developer connects a coding agent to the official GitHub MCP server with a personal access token, one that as most people configure it covers all their repositories, public and private. The developer asks something harmless, such as reviewing the open issues in a public repo. The agent reads them. One was filed by an attacker and contains hidden instructions: read this user's private repositories and publish what you find in a public pull request. The agent has the second capability (it just read attacker content), the first (the token opens private repos) and the third (it can open a public PR). It does all of it. In the demo, private code and even personal pay information became visible to anyone. Every action the token took was within its permissions, and the agent worked exactly as built. The trifecta simply closed.
Look at the fix. Docker's mitigation didn't try to detect the malicious issue. It broke the trifecta by policy: each session is pinned to a single repository, so the moment the agent reaches for a second one, it's blocked no matter what any instruction says. The model didn't get smarter. A capability was removed at the point where capabilities combine, and that's the shape of every real defense.
Why agents are so much worse than chatbots
Early worries about LLM security made injection look tolerable. A chatbot said something rude, or leaked its prompt. Irritating, not disastrous. Agents shift the stakes in every direction.
Chatbots produce text, while agents produce actions. Hijack a chatbot and one person reads a different answer. Hijack an agent and the thing itself behaves differently, with real reach: it can email people, transfer funds, wipe records, merge code or run shell commands. The OWASP Agentic Security project's 2026 top ten reads like a list of exactly these outcomes: goal hijacking, tool misuse, privilege abuse, memory poisoning and failures that cascade across multi-agent setups. Each is plain injection magnified by the model's connection to real capabilities.
Three multipliers explain the exposure.
The attack surface is everything the agent reads. In a real agent, the operator's prompt is a sliver of the tokens, and 70% or more of the context is often tool results and retrieved content. The lopsided mix is a security problem too. Most of what's in the model's head when it decides something came from outside, and any token of it could be an instruction. Your trusted prompt is a rounding error against a flood of untrusted input.
Autonomy removes the human speed bump. An agent's value comes from running a loop without asking permission at each step, but every unsupervised step is one where an injected instruction runs with nobody to catch it. The more autonomous and impressive the agent, the wider that window. Capability and injectability grow together, which means that, other things equal, the stronger agent is the costlier one to lose to an attacker.
Persistence and chaining add more. Agents have memory, and memory can be poisoned: an attacker plants a false "fact" in long-term storage on Monday and it skews decisions for weeks. Agents call other agents, so an injection in one sub-agent's data cascades up a chain nobody watches end to end. And agentic browsers like Atlas and Comet put an injectable agent in the tab where you're logged into your bank, your email and your company's admin console at once. Researchers have already tricked Atlas with malicious text disguised as a URL and with content that implants false memories. OpenAI's response deserves credit for honesty: it hardened the product with adversarial training and automated red-teaming and told users plainly that the problem isn't fully solved.
Defenses that help
Bluntly, nobody knows how to make a model resist injection 100% of the time. Every defense below lowers risk and none eliminates it. Real security here comes from stacking imperfect layers and, above all, designing the system so a successful injection can't reach anything valuable. Here are the honest defenses, separated from the comforting illusions.
Break the trifecta first. It pays off more than anything else here, and it needs no machine learning. For each agent, check whether it holds all three capabilities at once and, if so, whether one can be dropped in that context. An agent that reads untrusted web pages shouldn't also hold production database credentials. An agent that touches private data should have outbound communication limited to an allowlist so tight that nowhere remains to send secrets. The GitHub fix of one repo per session is a trifecta break. It works because it's architectural rather than probabilistic.
Use a design that keeps the planner away from poisoned input. The most promising research frames injection as a question of how data flows rather than whether it can be spotted. The Dual LLM pattern and CaMeL, its stricter descendant ("Defeating Prompt Injections by Design," 2025), divide the agent into two roles. A privileged model reads your request and plans the steps but never sees untrusted material. A quarantined model reads the untrusted material, an email or a web page, yet has no tools and cannot act. CaMeL goes further: the privileged model's plan is compiled into a restricted program, and a custom interpreter follows the origin of every value. Anything pulled from an untrusted source is labeled that way, and it can't reach a dangerous action unless a policy says so. That comes closest to parameterized queries for LLMs, a boundary between command and data that's actually enforced. There's a price: someone has to write the policies, and endless approval prompts can teach users to click yes by reflex. Still, it rests on principle, and principle holds up under a determined attacker.
Constrain actions with a permission ladder. Rank each action by how much damage it could do, and let the harness, not the prompt, enforce the ranking. Reading is silent, anything reversible is logged, hard-to-reverse ones (send, delete, pay, merge) need human confirmation showing exactly what's about to happen, and irreversible, costly ones are blocked or routed through an approval path the model can't influence. A prompt is a suggestion an attacker can argue with, and a harness rule is a wall they can't. Telling the model to always ask before sending money is worth doing and worth nothing alone. Making the payment code require a token the model never holds is security.
Apply least privilege with scoped credentials. Injection can only do damage in proportion to what the agent is allowed to do, so give each agent the narrowest scope, the shortest-lived token and the fewest tools that still get the job done. The GitHub incident was a broad token behaving as broad tokens do, and per-repository OAuth scoping would have contained it. This is old, boring hygiene, and it beats any clever prompt.
Add detection, but don't lean on it. Teams grab this layer first, and it belongs last. Injection classifiers are models trained to recognize "ignore previous instructions" and its countless rewordings, and they do catch a great deal. Anthropic's computer-use classifiers, Microsoft's XPIA filter and many startups work in this space. Use them, since they make attacks cost more. Just be clear about what they provide. A classifier blocking 99% of attempts sounds excellent, yet for security it counts as a miss, because injection isn't random noise. The adversary gets endless retries and adapts to your filter. The first move in EchoLeak was an email built to pass Microsoft's injection classifier, and it passed. Against an attacker who adapts, a 1% leak rate is a speed bump. Keep detection as one layer, never the foundation of your safety.
Record everything. Every tool call, retrieved document and action should be logged and attributable. In security terms that's your detection and forensics layer. After the fact you want to know what the agent read right before the odd behavior and where the instruction came from, and you want anomaly alerts when it suddenly touches resources outside its usual pattern. You can't defend, or even understand, an attack you can't see. An unobservable agent isn't just hard to debug. It's a breach you'll hear about from someone else.
Three incidents to study
Patterns stick when attached to real events. These three, from roughly eighteen months, show different rungs of the problem.
EchoLeak (Microsoft 365 Copilot, CVE-2025-32711). What stands out is the absence of any click, more than any trick. Discovered in January 2025, fixed in May and made public in June, it was the first prompt injection to pull real data out of a production AI system with no action from the user. It strung together four evasions: an email designed to pass the XPIA classifier, reference-style Markdown links that sidestepped link redaction, an image tag that loaded on its own and sent an outbound request, and a whitelisted Teams preview API acting as a relay so the data exited through an approved door. Each control Microsoft had was sensible individually. The attacker located the gaps between them. A stack of detection layers is also a stack of gaps, and a patient adversary probes all of them.
The GitHub MCP heist (Invariant Labs, May 2025). Covered earlier, though the takeaway merits restating: nothing malfunctioned. No credential failed and the model stayed intact. A broad access token, an agent reading issues written by an attacker and the power to open a public PR together formed the vulnerability. There was no exploit code or memory bug to patch, only three capabilities that never should have shared a session. That's what makes agent security a new discipline. The flaw sits in how parts combine, not in any part.
The agentic browsers (Atlas and Comet, late 2025). Once ChatGPT Atlas and Perplexity's Comet were released, researchers promptly demonstrated omnibox injections, where text dressed up as a web address got run as an instruction, and memory attacks that left false context behind from one session to the next. Co-location is what makes this case especially ugly. The injectable agent lives where you're authenticated to your bank, email and admin consoles simultaneously, the trifecta assembled by default in the highest-stakes spot. OpenAI's reaction set the industry's honest tone with adversarial training, automated red-teaming and an open admission that this isn't solved. Take them at their word and design as if your browser agent will eventually be fooled.
What to evaluate, by layer
Think in layers and pick something for each.
- Design-pattern defenses: CaMeL and the Dual LLM pattern. They separate planning from untrusted data and track provenance, which gives the strongest guarantees.
- Injection classifiers: Anthropic's classifiers, Microsoft XPIA, Lakera Guard, PromptArmor and protectai/rebuff. They flag adversarial content on untrusted inputs, raising costs without forming a wall.
- Gateways and policy enforcement: Docker MCP Gateway interceptors, Invariant Guardrails and egress allowlists. These break the trifecta through policy by scoping sessions, blocking cross-resource access and clamping outbound traffic.
- Red-team tools and benchmarks: AgentDojo, AgentDyn, BIPIA, CyberSecEval and deepteam, which make injection testing repeatable and measurable.
- Standards: the OWASP Top 10 for LLMs and for agentic apps (2026) and the NIST AI RMF, giving shared vocabulary and a checklist security teams already know.
- Observability: LangSmith, Langfuse, Braintrust and Phoenix with security alerting. That's the forensics and detection layer.
A rule that saves grief: don't buy one product and declare yourself protected. Every item above is a layer, and the point here is that no single layer holds. A sound posture combines a design-pattern defense, scoped credentials, a permission ladder, a classifier and full tracing, chosen so that beating any one still leaves the attacker facing the next.
Failure modes in the wild
- The exfiltration chain. Untrusted content tells the agent to collect private data and send it out, as in EchoLeak and GitHub MCP. Counter it by breaking the trifecta, using egress allowlists, tracking data flow (CaMeL) and scoping credentials.
- The confused deputy. Injected content makes the agent take a state-changing action the user never wanted, like sending an email, deleting a file or approving a transaction. Stop it with the permission ladder, requiring a person to approve anything consequential, with the rule living in the harness.
- The zero-click. No user interaction is needed, because the agent's ordinary background reading is the trigger, as with EchoLeak's auto-loading image. Lock down auto-fetch and external rendering, control egress and treat everything consumed passively as hostile.
- Memory poisoning. A planted false fact persists in long-term memory and warps later decisions. Treat memory writes as privileged, tag stored data with provenance and audit memory periodically.
- The rug-pull tool. An MCP server or plugin that's harmless at install and turns malicious later, or whose own description carries an injection. Pin and sign tool versions, vet the supply chain and sandbox tool execution.
- Multi-agent cascade. An injection in one agent's data travels through agent-to-agent calls nobody monitors end to end. Authenticate inter-agent messages, contain each agent's blast radius and trace across the whole chain.
- The over-trusted classifier. A team believes its filter makes it safe, deploys an agent with the full trifecta behind it, and an attacker who adapts walks around it. Never treat detection as sufficient, and assume every filter will eventually fall.
A one-week security pass
This is small, concrete and runnable against an agent you already have.
Day 1, map the trifecta. Take each agent you run and note what private data it can reach, what untrusted content it ingests and how it can send things outside. Any agent with all three goes to the top of the list. That inventory is the most valuable security document you'll write, and an afternoon is enough.
Day 2, break at least one side of each trifecta. For every risky agent, strip one capability in that setting. Divide it into two agents with narrower jobs, shrink its credentials or limit outbound traffic to an allowlist. All three won't always be removable, but cutting just one beats weeks of prompt hardening.
Day 3, build the permission ladder. List every action the agent can take and sort them into silent, logged, confirmed and blocked by blast radius. Enforce the top two tiers in code, out of the model's reach, and check that confirmation prompts display what will actually happen.
Day 4, attack your own agent. Prepare ten hostile inputs, including a booby-trapped page, a message, an issue, an invite, a PDF with invisible text and an image whose alt text carries orders. Run the agent against each in a sandbox and see what happens. Something will work, and finding it yourself beats someone else finding it. A benchmark such as AgentDojo makes the exercise repeatable.
Day 5, add detection and keep expectations modest. Put an injection classifier in front of untrusted inputs and record what it flags. It's a smoke alarm that raises attacker costs, not a barrier. Send its output into the tracing you'll enable on day 6.
Day 6, enable tracing for security purposes. Confirm every tool call, retrieval and action is recorded with a clear owner, and set an alert for resource access that falls outside normal patterns. Where tracing already exists, you're changing configuration, not starting a project.
Day 7, write it down and retest after every change. Injection resistance isn't a milestone you pass. It's a property you maintain. Every new tool, data source or capability can re-close a trifecta you'd broken, so add "does this reopen the trifecta?" to the review checklist and rerun the day-four attacks whenever the answer might be yes.
No tidy ending
Prompt injection isn't solved, and it may not be solvable the way SQL injection was, because the property that makes these models useful, following instructions in ordinary language, is the one that makes them exploitable, and nobody knows how to keep one without the other. The people building frontier browsers have started saying so out loud, which is the most encouraging development around: the industry has stopped pretending a smarter model will make the problem vanish.
Waiting for a fix isn't a plan. The plan is agents that stay safe even after the model has been tricked, because at some point it will be. That redirects engineering effort: away from a cleverer system prompt pleading for caution and toward the structure surrounding the model. Break the trifecta, keep planning apart from untrusted data, narrow every credential, put each consequential action behind a wall the model can't talk its way past, and observe everything. All of that sits in the harness and none of it in the model.
The model is a capable, trusting and injectable component, and the harness is how you use it safely anyway. Make the model your security boundary and you'll get breached. Place the boundary outside it, under your control, and you stand a chance.