Lesson 11 / 28
Cross-Site Scripting and Markdown/Link Exfiltration
Escape model text in the browser and watch for data leaking through images and links.
Rendering is an attack surface
If a chat UI inserts model output as raw HTML, a reply containing <img onerror=...> or a <script> tag runs in the user's browser and can steal sessions. Escape output before inserting it into a page (or render through a safe Markdown renderer that disables raw HTML) and set a strict Content Security Policy. A subtler attack uses Markdown images or links: an injected instruction makes the model output ; when the chat UI renders the image, the browser fetches that URL and sends the secret to the attacker with no click needed. Defences: do not auto-render images or links from untrusted domains (allow-list image hosts), strip or rewrite URLs with query strings in model output, and show the user the link text before it is followed.
Escaping a reply before rendering, run
I ran this with plain Python 3 (standard library only). All attacks here are harmless demonstrations on local data, using no real systems. The raw reply contains an <img onerror=...> element that would execute in a browser. After html.escape the angle brackets and quotes become entities, so the browser shows the text instead of running it.
import html
model_output = 'Thanks! <img src=x onerror="alert(document.cookie)"> Your order has shipped.'
print("raw into a page :", model_output)
print("escaped for a page:", html.escape(model_output))
Output:
raw into a page : Thanks! <img src=x onerror="alert(document.cookie)"> Your order has shipped. escaped for a page: Thanks! <img src=x onerror="alert(document.cookie)"> Your order has shipped.
Set a Content Security Policy
A strict CSP is a second line of defence if an escaping mistake slips through.
Quick check: How can a rendered Markdown image leak data?
- It cannot
- Images always contain viruses
- Images change the model weights
- The browser fetches the image URL, and the URL itself can carry a secret to the attacker
Answer
The browser fetches the image URL, and the URL itself can carry a secret to the attacker — No click is needed: rendering triggers the request.