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.

Timothy Lindblom

Founder, Newly

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.

Key facts, checked October 24, 2026
QuestionShort answer
What leaksWhatever the model was able to read and then wrote into an image address: chat history, a document, a private record.
What triggers itText the model reads that carries instructions. OWASP calls this prompt injection, risk LLM01.
Does the user tap anythingNo. Images load by themselves when the message is drawn.
Where the fix goesThe code that renders model output, plus a Content-Security-Policy on any web page that shows it.
What can you promiseNothing 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.

Request path: a model reply contains an image address, the chat screen loads it, and the outside server receives the data in the address. An allowlist at the renderer stops the load.Text the modelreads (a file)Model reply withan image addressChat screenrenders markdownOutside servergets the requestAllowlist check hereThe data rides in the address. Nobody taps anything.

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.

Two disclosed cases
CaseWhat the screen didVendor fix
Eclipse Theia AI chat, CVE-2026-22551Rendered 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, SalesBleedA 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.

Fix options compared
LayerWhat to doLimit
Renderer allowlistDrop 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-srcOn 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 takeKeep 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?

  1. Find the component that draws assistant messages. Search your project for the markdown library name and for an image rule.
  2. Read what it does with images. Dropped, allowlisted or loaded from anywhere are the three outcomes.
  3. On web pages that show chat, open the browser network panel, load the page and read the Content-Security-Policy response header.
  4. 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 each result means
What you seeStateNext step
Your test host logged a requestDetectedAdd the allowlist, then repeat the test.
You have not read the renderer or run the testUnknownDo steps 1 and 4. Unchecked stays unknown.
Renderer shows allowlist, test log is empty, header is narrowConfirmed by owner evidenceKeep 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.
  1. 1. The chat renderer either drops images from model output or loads only addresses on an allowlist.

    Evidence: the renderer file or config, plus a screenshot of an outside image showing as plain text in a test chat.

  2. 2. The allowlist names exact hosts you control. It has no wildcard, and no host that lets anyone upload or redirect.

    Evidence: the allowlist as it stands in your code, line by line.

  3. 3. Any web page that shows chat output sends a Content-Security-Policy with a narrow img-src, or has none because it has no web chat.

    Evidence: the response header copied from your browser's network panel, or a note that no web chat exists.

  4. 4. A test image address on a server you own, placed in a test conversation, produced no request in that server's log.

    Evidence: the server log for the test window, dated.

  5. 5. The model sees only data the signed-in user could already read, and it holds no secrets in its prompt.

    Evidence: the system prompt, the list of tools and what each one can read.

0 detected5 unknown0 confirmed by owner evidence

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.

  1. CVE-2026-22551: Eclipse Theia AI chat data exfiltration (GHSA-qwjm-9c66-w4q4), GitHub Advisory Database, published June 18, 2026
  2. SalesBleed: 0-click data exfiltration on Agentforce, Zenity Labs
  3. SalesBleed flaws in Salesforce Agentforce enabled zero-click data exfiltration, SecurityWeek
  4. LLM01: Prompt Injection, OWASP GenAI Security Project
  5. OWASP Top 10 for LLM Applications 2026, OWASP GenAI Security Project
  6. ATT&CK v20 updates, October 2026 (T1698, T1699, T1700), MITRE ATT&CK
  7. Content-Security-Policy: img-src, MDN Web Docs
  8. Content-Security-Policy: connect-src, MDN Web Docs
  9. Content Security Policy (CSP) guide, MDN Web Docs
  10. How to set a Content Security Policy for your Next.js application, Next.js docs, last updated March 20, 2026
  11. react-markdown: urlTransform, allowedElements and skipHtml, remarkjs on GitHub

Continue Learning