Threat research

Markdown-link exfiltration in the wild

The same three-line attack has been found, fixed, and found again in almost every major AI assistant since 2023. It keeps working because the fixes address the payload, not the channel.

Almost every AI assistant renders its own output. Markdown goes in, formatted HTML comes out, and images in that Markdown are fetched automatically so the user sees a picture rather than a link. That last behaviour is the whole vulnerability. If an attacker can get one line of Markdown into the model's output, and that line is an image whose URL carries data the model can see, then the rendering client performs the exfiltration on the attacker's behalf. No click, no download, no malware.

The primitive: an image reference whose URL embeds context the model has access to. The victim's browser fetches it to display the image, and the fetch itself delivers the data to the attacker's server. The attacker never touches the victim's network.

The shape of the bug

Three conditions have to line up, and they line up constantly in retrieval and agent architectures:

  1. The model can see something worth stealing. Chat history, a retrieved document, memory, a tool result, a system prompt.
  2. An attacker can influence the model's input. Usually indirectly: a web page the agent browses, an email in the mailbox it summarizes, a document in the corpus it retrieves from. This is indirect prompt injection, OWASP's LLM01.
  3. The output surface auto-fetches remote URLs. Markdown image rendering is the usual culprit, but any auto-loaded resource works.

Simon Willison calls the general pattern the lethal trifecta. What makes the Markdown variant so persistent is that the third leg, the exfiltration path, is not a tool anyone deliberately granted the agent. It is the user interface. Teams audit tool permissions carefully and then ship a renderer that will fetch any URL the model emits.

A short field history

This is not a hypothetical class. Johann Rehberger has been documenting the same primitive across products for years, and the list of affected systems reads like a directory of the industry:

Willison maintains a running tag for exfiltration attacks that has not gone quiet. The pattern is not that vendors are careless. It is that each fix lands on one product's rendering path while the primitive stays valid everywhere else.

Why the fixes keep partially failing

Three families of mitigation have been tried in production. Each helps. None of them closes the channel.

Destination allow-listing. OpenAI's url_safe check, introduced in late 2023, validates a URL before the client fetches it. It cuts the attack down but does not remove it: Rehberger showed a bypass that leaks one character per request, and any domain on the allow-list is still a usable destination for data smuggled into a path or query string. Microsoft's published defence stack layers similar controls.

Link and image redaction. Stripping external images from model output is effective against the naive form and was exactly what EchoLeak worked around, using reference-style Markdown, where the link target is declared separately from where it is used, so the redaction pattern did not match.

Injection classifiers. Microsoft ran an XPIA filter in front of Copilot. EchoLeak's payload simply never sounded like an injection: it was phrased as a note to a human reader and mentioned neither the assistant nor its instructions. More generally, Nasr and colleagues showed in 2025 that twelve published defences fall to adaptive attacks with success rates above 90%, despite most of them reporting near-zero rates against static test sets. A classifier raises the cost of an attack. It does not bound it.

The pattern: every one of these mitigations reasons about the payload, which the attacker controls and can rephrase indefinitely. None of them reasons about the channel, which the defender controls.

What actually bounds it

The controls that hold treat outbound requests as a policy decision rather than a rendering detail, and they are deterministic, so an attacker cannot rephrase past them:

  • No model-derived data in outbound URLs. If a URL's path, query, or fragment contains bytes derived from model context, it does not get fetched. This is a structural check, not a judgement about intent.
  • Provenance on the destination. Following the capability model in CaMeL (Google DeepMind and ETH Zurich, 2025), an outbound destination is permitted only if it originated from trusted input, never from untrusted retrieved content. A URL that arrived in the attacker's document is not a valid destination no matter how the model presents it.
  • Redact before render. Regulated data leaves the response before any surface can serialise it into a link.
  • Proxy the fetch. Where remote images are a genuine product requirement, fetch them server-side through a component that strips query strings and refuses attacker-supplied hosts.
  • Evidence. Record every blocked egress with the policy version that blocked it. A control you cannot produce evidence for is an assertion.

The useful property of these controls is that they hold after the injection succeeds. The model can be fully persuaded and still fail to get the data out.

Where Intercept fits

Intercept inspects responses on the way out, not just prompts on the way in, and applies egress policy at the boundary: data-bearing external links are blocked, regulated data is redacted before it can be serialised into any URL, and destinations are checked against provenance rather than against a phrasing pattern. Each decision is signed so the block is provable at review time. The detector work still matters, but it is the deterministic egress layer that decides whether a successful injection becomes a breach.

Next: see Guard for egress inspection, or Anatomy of an Indirect Prompt Injection for how the payload gets in.