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  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:
- Sanitizer— DOMPurify / bleach strips active content and disallowed markup before anything reaches the DOM
- Host allowlist— URL and image-host allowlisting; only hosts you control and cannot coerce into fetching attacker URLs
- Server-side proxy— images and links are fetched and re-served through your own origin, never the client hitting an arbitrary host
- Strict CSP— img-src / connect-src locked down so an auto-fetched image or beacon to an attacker host is blocked by the browser
Trust Boundaries
- Model output → sanitizer — the model's markup is untrusted; it enters here and is stripped of active content before touching the DOM
- Sanitizer → allowlist + proxy — only URLs on hosts you control survive; everything else is fetched through your origin or dropped
- Proxy → CSP-locked render — the browser itself refuses img-src / connect-src to any attacker host — the last line that catches what slipped through
Threat Surface
| Threat | Vector | Impact |
|---|---|---|
| Zero-click image exfil | auto-fetched markdown image with secret data in the query string | Private data leaks to attacker logs with no user interaction |
| Allowlisted-host leak | attacker uses a permitted CDN or a proxy that mirrors arbitrary URLs | Exfiltration through a host the allowlist trusts |
| Uncovered render surface | an email digest, PDF export, or unfurl that skips the hardened path | The channel stays open for the whole system |
| XSS from output | active content in model output rendered without sanitization | Script 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
- Output Filtering— constraining and validating what the model is allowed to emit before it renders
- Least Privilege & Isolation— removing the exfiltration leg so a leaked channel has nothing to steal
- Guardrail Scanners— detection layered in front of the render surface
- Taint Tracking & Provenance— knowing which output spans came from untrusted content before you render them
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.