The Lethal Trifecta

A46
Concepts · Agentic AI Explained

The lethal trifecta.

Three capabilities are individually ordinary and jointly catastrophic: access to private data, exposure to untrusted content, and a way to send data outwards. Simon Willison named the combination in June 2025, and its whole value is that it is a conjunction — you cannot reliably stop prompt injection, but you can break one of three legs, and the cheapest leg to break is almost never the one teams reach for first.

Step 1The three legs, stated precisely enough to audit.

An agent sits in the trifecta when all three of the following are true within one context:

  • Access to private data. Anything the attacker wants and cannot otherwise read: your email, a customer database, source code, a secrets file, an internal wiki. Read access through a tool counts; so does data already sitting in the context window from an earlier turn.
  • Exposure to untrusted content. Any text the agent will process that an attacker can influence — a fetched web page, a PDF a stranger uploaded, a GitHub issue, a calendar invite, a commit message, the body of an inbound email. The bar is "can an outsider write any of the bytes the model reads", which is lower than most teams assume.
  • An outbound channel. Any way for bytes to leave: an HTTP request, a send-email tool, a database write a third party can read, a commit, a Slack message. Image rendering counts — a Markdown image whose URL the model composes is an exfiltration channel, and it is the one that has shipped in product after product.

The phrase is also used in AI-safety writing for an unrelated set of risk factors. In agent security it means precisely these three, and the distinction is worth keeping because the remedies have nothing in common.

Step 2Why the conjunction is the useful object, not the injection.

Prompt injection works because instructions and data arrive on the same channel, and no amount of training reliably teaches a model to treat one paragraph as a command and the next as inert text — see prompt injection 101. That is a property of the architecture, not a bug awaiting a patch, which is why a detector is a probability reduction rather than a control.

The trifecta reframes the problem from "prevent the injection" to "ensure a successful injection has nowhere to go". Each leg is a boolean you can inspect in your own system, without reasoning about attacker creativity. Drop any one of them and the worst case changes shape:

  • Private data but no untrusted content: the agent only ever reads text you authored. Nothing to inject.
  • Untrusted content but no private data: the injection succeeds and steals a public web page.
  • Both, but no outbound channel: the injection succeeds, the agent reads your secrets, and the only consumer of the result is a human staring at a screen.

"No outbound channel" is the leg people believe they have and usually do not. An agent that can only write to a logging service has an outbound channel if anyone outside can read the logs; an agent with no network tools has one if its answer is rendered as HTML somewhere a URL will be fetched. Enumerate the channel by asking who can read each thing the agent can write, not by listing its tools.

Step 3Egress is usually the cheapest leg to cut, and the last one teams try.

Faced with the trifecta, the instinct is to attack leg two: filter the untrusted content, add an injection classifier, wrap documents in delimiters telling the model to ignore instructions inside. That work has the worst cost-to-benefit ratio of the three, because it buys a probability against an adversary who gets unlimited retries and only needs one success.

Leg one is the next instinct and is genuinely valuable but expensive: narrowing what the agent can read means per-task permissions and often a data model that was not built for them. It is the right long-term investment and it is a quarter of work, not an afternoon.

Leg three is the one with the best ratio, because an allowlist of destinations is a deterministic control that does not care how clever the prompt was. A proxy that permits exactly the four hosts your agent legitimately posts to converts "exfiltrate the secrets anywhere" into "exfiltrate the secrets to a host that logs the request and belongs to you". Egress control for agents is the mechanical version; covert channels is the honest accounting of what still leaks through a tight allowlist.

# Leg 3, as policy rather than hope
egress:
  default: deny
  allow:
    - api.internal.example.com      # the one API this agent writes to
    - "*.githubusercontent.com"     # read-only, still an exfil channel via URL path
  render:
    markdown_images: false          # the channel nobody remembers to close

Step 4The trifecta is a property of a context, not of an agent.

This is the part that makes the model hard to apply and the part most worth internalising. The three legs do not have to be granted together to end up together. They accumulate inside a running context, and several ordinary mechanisms recombine them after you have carefully split them:

  • A subagent that returns text. You gave the reader subagent untrusted content and no credentials, and the writer subagent credentials and no untrusted content. The reader's output is untrusted content, and it lands in the writer's context — see what a subagent inherits.
  • Memory and compaction. A private value read in turn three survives into turn forty via compaction or a memory write, long after the turn that justified reading it ended.
  • A tool added later. Legs are granted by different people at different times. The agent that was safe in March acquires a send-email tool in June, and nobody re-ran the analysis because adding a tool is not a security change on anyone's checklist.

The operational consequence: the trifecta has to be evaluated per context window per deployment, and the thing to put under change control is the third leg, because it is the one whose addition is least likely to feel risky. Context taint tracking is the systematic version — mark data by origin and refuse the outbound call when tainted data is in scope.

The one-page version to run on any agent you own: list every source of bytes the model reads and mark which an outsider can influence; list everything the agent can read and mark what is not already public; list everything the agent can write and ask who can read it. If all three lists are non-empty, pick the outbound list and cut it to an allowlist this week — then start the slower work of per-task read scoping. Do not start with an injection classifier; it is the only one of the three that cannot be made to hold.

Related: ambient authority for why agents hold more than you granted, blast radius for sizing the worst case, confused deputy for the authorization framing of the same failure, and prompt injection defense for the full defensive stack.