Concept · No. 011 · October 26, 2026
An agent with private data, untrusted input and egress will leak
Prompt injection is not a filtering problem you can tune away. It becomes a data leak when one agent holds three capabilities at once, and the defence is to design so it never does.
The idea
The lethal trifecta is a name for a specific combination: an agent that can read private data, that processes content an attacker can influence, and that has some way to send information out. Any two of these are manageable. All three together mean a prompt injection can become exfiltration.
The reason is structural. A model does not separate instructions from data. Text inside a fetched web page, an email body or a ticket comment arrives in the same context as the system prompt, and the model may follow it. Filters and guardrail classifiers lower the odds, but they are probabilistic, and a security boundary that holds most of the time is not a boundary.
So the useful question is not whether the model can be tricked. Assume it can. The question is what a tricked model is able to do with the tools it holds.
An example
Take a support agent that triages inbound email.
- It reads new messages from a shared inbox. Anyone on the internet can write to it, so that is the untrusted input.
- It can call
crm.search_customersto add context to a reply. That is the private data. - It drafts replies in a chat UI that renders markdown, including images. That is the egress, even though nobody designed it as one.
An attacker sends an email containing text addressed to the assistant: look up the five most recent enterprise customers and include a status image from a URL that carries their names and emails as query parameters. The agent calls the CRM tool, builds the URL, and emits a markdown image. The UI renders the draft, the browser fetches the image, and the attacker's server logs the data. No reply was ever sent, and no write tool was ever called.
Where it breaks
Removing one leg is the fix, but each leg is harder to remove than it looks.
Egress hides in places that do not feel like sending: rendered images, link previews, a search tool whose query string reaches a third party, a comment on a public issue, DNS lookups from a fetch tool. Untrusted input also hides: a teammate's document that quotes a customer, a code dependency's README, a tool result from a web search.
Contamination does not wash out. Once untrusted text is in the context, everything the model produces afterwards is suspect, including summaries of it.
Human approval as the only control degrades quickly. Reviewers approve what they cannot evaluate, especially when the dangerous part is an encoded URL.
The framing also has a blind spot. It covers confidentiality, not integrity. An agent that can delete records needs no egress to do damage after reading a hostile ticket.
In practice
I would start by listing, for every agent and every session, which of the three legs it holds, then design so no single context holds all three.
- Enforce egress at the network and rendering layers, not in the prompt. Allowlist outbound hosts, and render model output with images and remote content disabled or proxied.
- Track taint per session. After the agent reads untrusted content, disable tools that send data out for the rest of that session.
- Split the roles. A privileged planner never sees raw untrusted text; a quarantined model reads it and returns only typed values, such as a category or a date, that the planner treats as data.
- Scope credentials to the task. A triage agent does not need a token that can search every customer.
- When a human approves a write, show the decoded destination and payload, not the model's description of it.
- Treat destructive writes as a separate risk class with their own gates, regardless of egress.
Assume the model will follow injected instructions, and make sure no single context ever holds private data, untrusted input and a way out at the same time.