RAG and data
Data exfiltration through markdown images in LLM apps
LLM data exfiltration via markdown images: how a rendered image carries chat data out, five documented cases from 2023 to 2025, and the fixes that close it.
Short answer
Markdown image exfiltration works when an LLM app renders a model-written image link and the browser fetches it with no click. The image URL can carry chat data to a server the attacker controls. Close the channel in the output layer: block or proxy remote images, restrict links, set a strict content security policy, and test each renderer.
Markdown image exfiltration is a way to move data out of an LLM app without any click from the user. The app renders a model-written image tag. The browser fetches the image URL. If the URL holds private data, that data reaches a server the attacker runs.
The weakness sits in output handling: the app renders what the model wrote. OWASP lists the area as Improper Output Handling (LLM10:2026, LLM05:2025).
How does markdown image exfiltration work?#
Untrusted text tells the model to write an image tag, and the tag's URL includes data from the chat. The app renders the tag, the browser requests the URL, and the data arrives in the attacker's server log. Three conditions must all hold:
- Untrusted content reaches the model. A web page, email, document, ticket or tool result carries hidden instructions. This is indirect prompt injection.
- The model can see something worth taking. Chat history, retrieved documents, a memory store or a secret in context.
- The app lets the browser call an arbitrary host. The renderer shows remote images, or the content security policy allows the destination.
In the chain, step 3 is the one the app must stop.
A page, file, email or chat message holds hidden instructions. The model reads it with the user's data.
It writes a markdown image whose URL contains text from the conversation.
The renderer treats the model's output as trusted markup and requests the remote URL.
No click is needed. The URL, with the data in it, goes to a host the attacker controls.
The user sees a broken or tiny image, or nothing at all.
Lab illustration
This is a demonstration of the pattern only. No real customer system was tested, and the host is a placeholder.
A rendered tag of this shape is the whole mechanism: an image whose address is https://collector.example.com/pixel.png followed by a query string that holds a short piece of the chat. The browser only needs permission to load the address.
Which real cases show this?#
Five cases from 2023 to 2025 show the same pattern in different products, each with a primary source and a date.
| Date | Product | Source | What the source reports |
|---|---|---|---|
| 16 May 2023 | ChatGPT with plugins | Embrace The Red | Injected instructions made the chat render an image whose URL carried chat history. Reported 9 April 2023. OpenAI answered at the time that image markdown injection was a feature and no change was planned. |
| 18 June 2023 | Bing Chat | Embrace The Red | Reported to MSRC 8 April 2023. Microsoft told the author of a fix on 15 June 2023. The author links the fix in part to a new content security policy that allows images only from a few Bing domains. |
| 20 August 2024 | Slack AI | PromptArmor | Text in a public channel made Slack AI show a link that carried a secret from a private channel. Slack said public-channel content is visible to workspace members and called this intended behavior. |
| September 2024 | ChatGPT macOS app | Embrace The Red | Rehberger reported that injected memory instructions could send every later chat out through image URLs. OpenAI fixed the exfiltration path in app version 1.2024.247, per the post. |
| 11 June 2025 | Microsoft 365 Copilot (EchoLeak, CVE-2025-32711) | NVD, arXiv paper | NVD records the CVE. The paper describes a chain that got past an injection classifier, link redaction (via reference-style markdown) and a content security policy, and used an auto-fetched image. |
The fixes in the Bing and ChatGPT cases targeted the output channel. The EchoLeak chain beat several controls in a row, so one filter is a weak plan. The paper's "first real-world zero-click" label is the authors' own claim.
For memory-based persistence, see agent memory poisoning.
How do you fix it?#
Control what the output layer renders and what the browser may fetch, and use several controls, because one filter can be bypassed.
| Fix pattern | What to do | Limit |
|---|---|---|
| Do not render remote images | Show images only from your own storage. Replace others with text or a placeholder. | Needs product agreement |
| Image proxy with allowlist | Fetch approved hosts on the server. Strip query strings. Never forward cookies. | A proxy that follows redirects can be misused |
| Content security policy | Set img-src to named hosts. Avoid wildcard hosts and open-redirect hosts. |
EchoLeak used an allowed host that redirected |
| Link policy | Show the real destination. Block or warn on unapproved hosts. Do not auto-open links. | Users still click |
| Sanitize markdown | Parse markdown into a safe tree. Drop raw HTML. Handle reference-style links too. | Each parser has edge cases |
| Strip URL data | Remove query strings and long paths from model-written URLs before rendering. | Data can hide in the path or host name |
| Separate instructions from data | Mark retrieved text as data. Lower the chance the model follows it. | Does not remove the risk |
| Gate tools that fetch URLs | Treat a URL fetch as an outbound send. Limit hosts and ask for approval. | Needs a good approval design |
| Monitor | Log outbound image and link hosts per answer. Alert on new hosts. | Detection, not prevention |
Important
Do not rely on a model instruction such as "never output images". An injected instruction can override it. The controls above must run outside the model.
How do testers check for this channel?#
Testers check each place the app renders model output, on each client, with a harmless collector they control. They report what the app fetched, not what the model said.
- List the renderers. Web app, mobile app, desktop app, email digests, Slack or Teams bots, exported PDFs, admin consoles. Each can parse markdown in its own way.
- List the content sources. Which untrusted text can reach the model? Documents, web pages, email, tickets, tool results, memory.
- Plant a marker. In a test account, place a benign instruction that asks the model to include a marker image from a collector host you own. Use test data only.
- Observe. Check the network log of the client and your collector log. Did the client request the host? Was it a redirect? Did a query string or path carry any text?
- Vary the form. Try inline links, reference-style links, autolinks and raw HTML where the renderer allows it. Record which forms the sanitizer handles.
- Check other exits. Link previews, tool calls that fetch URLs, and email or message sends.
- Check the policy. Read the CSP headers. Look for wildcard hosts and hosts with open redirects.
A finding needs evidence of a real request that carried test data to the collector. A rendered tag with no request is a lower finding. Report controls that held, such as a proxy that stripped query strings.
What should you do next?#
Inventory every surface that renders model output, close the image path, then review the other outbound paths. Add the check to your release tests for each new client.
No set of controls proves that data cannot leave. The UK NCSC wrote in December 2025 that prompt injection may never be fully mitigated, and that the aim is to reduce likelihood and impact. Output controls reduce impact when injection succeeds.
Excession Labs tests these channels in scoped LLM application security assessments, with written authorization from the system owner. For the wider class, see our prompt injection guide and our RAG security guide.
Questions
Does the user have to click anything?
Not for images. A browser fetches an image URL when it renders the page. That request carries any data placed in the URL. Links need a click, so images are the more dangerous channel.
Is this a bug in the model?
It is a weakness in the app. The model writes text that the app renders without control. Fixes belong in output handling and rendering, with a content security policy as a second layer.
Do non-image channels exist?
Yes. Clickable links, tool calls that fetch URLs, email sends and link previews can all carry data out. Close the image path first, then review every outbound path the app has.
Will blocking all remote images fix prompt injection?
No. It closes one exit. Injected instructions can still change answers or trigger tools. Treat it as one control in a set, and review the other paths.
Sources
- Embrace The Red (Johann Rehberger), ChatGPT Plugins: Data Exfiltration via Images and Cross Plugin Request Forgery (16 May 2023) (checked 9 November 2026)
- Embrace The Red (Johann Rehberger), Bing Chat: Data Exfiltration Exploit Explained and Fix (18 June 2023) (checked 9 November 2026)
- Embrace The Red (Johann Rehberger), Spyware Injection Into Your ChatGPT's Long-Term Memory (SpAIware) (September 2024) (checked 9 November 2026)
- Reddy and Gujral, EchoLeak: The First Real-World Zero-Click Prompt Injection Exploit in a Production LLM System (arXiv:2509.10540, September 2025) (checked 9 November 2026)
- NVD: CVE-2025-32711 (published 11 June 2025) (checked 9 November 2026)
- PromptArmor, Data Exfiltration from Slack AI via indirect prompt injection (20 August 2024) (checked 9 November 2026)
- UK NCSC, Prompt injection is not SQL injection (8 December 2025) (checked 9 November 2026)
- OWASP Top 10 for LLM Applications 2026: LLM10 Improper Output Handling (checked 9 November 2026)