Markdown Image Exfiltration in Chat UIs: The Allowlist and CSP Fix
How a chat screen can send data to a stranger by rendering a picture, two dated 2026 cases, and the fix: an allowlist in your renderer plus a narrow img-src.
Published Last updated
If your app shows AI replies as markdown, a reply can contain an image address, and the screen fetches it as soon as the message appears. If the address holds text from the conversation, it leaves your app with no tap. The fix is to load images only from hosts you name, in the renderer and, on the web, in a narrow img-src.
| Question | Short answer |
|---|---|
| What leaks | Whatever the model was able to read and then wrote into an image address: chat history, a document, a private record. |
| What triggers it | Text the model reads that carries instructions. OWASP calls this prompt injection, risk LLM01. |
| Does the user tap anything | No. Images load by themselves when the message is drawn. |
| Where the fix goes | The code that renders model output, plus a Content-Security-Policy on any web page that shows it. |
| What can you promise | Nothing is guaranteed. A closed image path removes one channel. Others, such as links, remain. |
What is markdown image exfiltration in a chat screen?
Markdown lets a reply include a picture by giving its web address. The chat screen then asks that address for the picture. If an attacker can get the model to build an address that ends in a server they run, and to put conversation data in the address text, the request itself is the leak. The picture does not need to exist.
OWASP describes this in its prompt injection entry: a user asks an LLM to summarize a web page, hidden instructions make it insert an image linking to a URL, and the private conversation is exfiltrated. MITRE added T1700 AI-Enabled Exfiltration to ATT&CK on October 13, 2026, alongside direct and indirect prompt injection.
Three conditions have to line up. The model reads text from someone other than the signed-in user. It can see something worth taking. The screen loads images from any host. Removing any one of them breaks the path, and the third is the one you control with a few lines.
Has this happened in real products?
Two dated records, both fixed by the vendor. Neither is a verdict on the product's overall state, and the Salesforce vendor statement says it has no evidence of use against any customer.
| Case | What the screen did | Vendor fix |
|---|---|---|
| Eclipse Theia AI chat, CVE-2026-22551 | Rendered markdown image tags from AI replies without restriction, so a request to an outside address went out. Moderate, CVSS 6.7, published June 18, 2026. | 1.71.0 enforces workspace trust and turns the AI features off in untrusted workspaces. |
| Salesforce Agentforce, SalesBleed | A Trusted URLs filter was meant to strip outside links from replies. Zenity Labs reports it missed some top-level domains and read where a URL ends differently from the browser. | Reported June 1, 2026. Salesforce told SecurityWeek the issues were addressed by August 19, 2026. |
The second row carries the lesson for your own code. A filter that tries to spot bad addresses by pattern can disagree with the browser about what an address is. A list of hosts you allow has no such gap, because anything not on it is dropped.
What is the fix?
Three layers, cheapest first. Newly builds React Native and Expo apps, and you can download the code or connect your own repository, so the renderer is code you can open and change. Where a chat also appears on a website, the header layer applies too.
| Layer | What to do | Limit |
|---|---|---|
| Renderer allowlist | Drop images from model output, or keep only addresses whose host is on a short list you wrote. In a web renderer such as react-markdown, the options for this are urlTransform and disallowedElements, and raw HTML is off unless you add a plugin for it. | Works in native and web. Only as good as the list: no wildcards, no hosts that redirect or accept uploads. |
| CSP img-src | On web pages that show chat output, set img-src to your own origin and named hosts. MDN notes img-src falls back to default-src when absent. | Browsers only. Allowing the https: scheme or data: is broad. Next.js's own example uses img-src 'self' blob: data:. |
| Less for the model to take | Keep secrets out of prompts. Give the model only what the signed-in user may read. Ask for approval before privileged actions, as OWASP advises. | Limits the damage of any channel, including ones you did not think of. |
Two header details that trip people up
connect-src covers fetch, WebSocket and similar calls, and MDN says it does not control image loads. Image loads are img-src. Also, a header is a second line. MDN's guide says to sanitize and set a CSP together. To try a policy without breaking pages, send it as Content-Security-Policy-Report-Only first. If you add nonces for scripts, the Next.js guide notes every page must then render dynamically.
How do I check my own app, and what does each result mean?
- Find the component that draws assistant messages. Search your project for the markdown library name and for an image rule.
- Read what it does with images. Dropped, allowlisted or loaded from anywhere are the three outcomes.
- On web pages that show chat, open the browser network panel, load the page and read the Content-Security-Policy response header.
- Test it yourself. Put an image address on a server you own into a test conversation and read that server's log. A request from your own device means images load from outside.
| What you see | State | Next step |
|---|---|---|
| Your test host logged a request | Detected | Add the allowlist, then repeat the test. |
| You have not read the renderer or run the test | Unknown | Do steps 1 and 4. Unchecked stays unknown. |
| Renderer shows allowlist, test log is empty, header is narrow | Confirmed by owner evidence | Keep the log and the header copy with a date. Repeat after renderer upgrades. |
Only the first case in the table is a finding. Confirmed describes the one channel you tested, on the day you tested it. It says nothing about links, tools or storage. For the other chat checks, see the chatbot self-test, and for the wider list start with the 15-minute app check.
Where do you stand?
Everything starts as unknown. Move an item to confirmed only when you hold the evidence listed under it.
Your image-path check
- Detected: You found the problem.
- Unknown: Not checked yet, or the check could not tell.
- Confirmed by owner evidence: You checked and kept the proof.
Your answers stay in this browser tab. Nothing is saved or sent.
Frequently asked questions
Can a Content-Security-Policy header alone stop this?
Only for the web part of your product, and only if img-src is narrow. A header is read by browsers. A native screen in a React Native app loads images through your own code, so the allowlist has to live in the renderer there. Use both where both exist.
Is it enough to tell the model never to output images?
No. An instruction in a prompt is a request the model can be talked out of, and OWASP lists prompt injection as the first risk for LLM apps. Enforce the rule in code that runs after the model, where text from a stranger cannot change it.
Do links carry the same risk as images?
Differently. An image loads on its own when the message appears, so nobody has to click. A link needs a tap, but the address can still carry data. Show the real destination for links and consider an extra tap before opening anything outside your allowlist.
Does this matter if my chat is private to one signed-in user?
Yes, if the model reads anything the user did not write: an uploaded file, a pasted web page, a support ticket, an email. Text in those can steer the model, so the signed-in user may never see why their chat contacted an outside server.
Will blocking images hurt my chat feature?
Only if you rely on pictures from outside hosts. Images you store yourself, such as charts or product photos in your own storage bucket, stay on the allowlist. The model can still write text, lists and tables.
Sources
All sources checked October 24, 2026.
- CVE-2026-22551: Eclipse Theia AI chat data exfiltration (GHSA-qwjm-9c66-w4q4), GitHub Advisory Database, published June 18, 2026
- SalesBleed: 0-click data exfiltration on Agentforce, Zenity Labs
- SalesBleed flaws in Salesforce Agentforce enabled zero-click data exfiltration, SecurityWeek
- LLM01: Prompt Injection, OWASP GenAI Security Project
- OWASP Top 10 for LLM Applications 2026, OWASP GenAI Security Project
- ATT&CK v20 updates, October 2026 (T1698, T1699, T1700), MITRE ATT&CK
- Content-Security-Policy: img-src, MDN Web Docs
- Content-Security-Policy: connect-src, MDN Web Docs
- Content Security Policy (CSP) guide, MDN Web Docs
- How to set a Content Security Policy for your Next.js application, Next.js docs, last updated March 20, 2026
- react-markdown: urlTransform, allowedElements and skipHtml, remarkjs on GitHub
