This article describes a security lab demonstrating XSS vulnerability in AI chatbot interfaces that render Markdown output. The vulnerability arises not from the language model itself, but from unsafe rendering of model output in the web page: a naive Markdown-to-HTML converter fails to properly quote URLs in image tags, creating an attack surface. The lab teaches that LLM output must be treated as untrusted input and that security relies on the display layer, not the model's filtering.
- Markdown image syntax becomes XSS vectorDocBot filters raw HTML but allows Markdown image syntax  because developers believed images are not executable. However, when the renderer converts this to an HTML img tag without properly quoting the URL attribute, an attacker can craft a Markdown image whose URL parameter closes the src attribute early, injects an onerror handler, and reopens a harmless attribute to keep the tag valid.
- LLM becomes reliable payload delivery mechanismThe model is not itself vulnerable but acts as a faithful payload generator. By requesting the model show Markdown syntax for an image with a specific URL (framed as a formatting question), the model outputs the attacker-supplied string verbatim because it is being accurate and helpful. The model's output filter considers Markdown safe and does not block it, making the model a dependable channel to get hostile input into the renderer.
- Onerror handler executes on broken imagesWhen an img tag points to an invalid or unreachable URL, the browser fires the onerror event handler. An attacker pointing the image source to a malformed value guarantees the handler fires, executing whatever JavaScript the attacker has injected into the onerror attribute.
- Defense-in-depth requires multiple layersThe article recommends sanitizing rendered HTML after Markdown conversion using tools like DOMPurify, using a strict renderer with disabled HTML and escaped URLs, adding Content Security Policy to block inline event handlers, and optionally rendering chat in a sandboxed iframe. No single layer is sufficient; CSP alone is not enough because DOMPurify may miss edge cases, and a good renderer alone does not protect against converter bugs.
- Two systems each assumed the other was safeThe vulnerability exists because the model's filter trusted Markdown as harmless output and the Markdown renderer trusted the model would never emit hostile input. This shared false assumption created the bug: neither system validated or sanitized, each relying on the other to enforce security.
- Markdown renderers enable data exfiltrationResearcher Johann Rehberger demonstrated in 2023 that chat interfaces rendering Markdown could be induced to load an attacker-controlled URL with conversation data in the query string, silently leaking sensitive information. This prompted some vendors to restrict image rendering in chat.
- Model output is untrusted front-end inputThe core principle is that LLM output must be treated exactly like any user-supplied input: sanitized after rendering, every time. The model is not the vulnerability; the page that trusts its output without proper validation is.
Bottom line: XSS in AI chatbot interfaces exploits unsafe Markdown rendering, not the model itself—treat model output as untrusted input and sanitize after conversion using multiple defense layers including DOMPurify, strict renderers, CSP, and sandboxing. This is a real vulnerability in production AI products requiring proper disclosure and remediation.
📋 In this walkthrough
What this lab teaches
LLM output is untrusted input to your front end. If the page converts the model’s reply from Markdown to HTML and inserts it into the DOM, every edge case in that conversion becomes your attack surface.
The lab combines two skills: getting the model to produce a very specific string, and knowing which string breaks the renderer.
How DocBot is set up
DocBot’s replies pass through a naive Markdown renderer. Raw HTML is filtered, but  is allowed “because images aren’t executable”. The renderer expands it into an <img> tag without properly quoting the URL. The lab is solved when your payload triggers the page’s detection function.
The attack approach
- Do not ask the bot for HTML. Ask it for Markdown, which it considers safe.
- Study how an image tag works: if the image fails to load, its
onerrorhandler runs. - Your goal is a Markdown image whose “URL” closes the attribute early and adds an
onerrorhandler. - Frame the request as a formatting question. The model is happy to show you “the Markdown for an image at this exact URL”, verbatim.
Full walkthrough (spoiler)
Stuck after a few attempts? Open the steps below. Solving it yourself first is worth more XP, and more learning.
Show the step-by-step solution
- Ask DocBot to show you the Markdown image syntax for an image whose alt text is “logo” and whose source URL is exactly a string you supply.
- Make that “URL” a value that ends the src attribute with a quote, then adds an onerror attribute calling the lab’s detection function, then reopens a harmless attribute so the tag stays valid.
- DocBot prints the Markdown faithfully. The renderer turns it into an image tag with your handler. The broken image fires onerror, and the lab registers the XSS.
Why it works
Two systems each assumed the other was the safety layer. The model’s filter assumed Markdown was harmless; the renderer assumed the model would never emit hostile URLs. The model was simply being helpful and accurate, which is exactly what makes it a reliable payload generator.
Where this happens in the real world
Markdown rendering is also a known data-exfiltration channel. Security researcher Johann Rehberger showed in 2023 that chat interfaces rendering Markdown images could be induced to load an attacker’s URL with conversation data in the query string, leaking it silently. Several vendors restricted image rendering in response.
How to defend against it
- Sanitise after conversion. Run the rendered HTML through DOMPurify or an equivalent before inserting it.
- Use a strict renderer: HTML disabled, URLs quote-escaped, and an allowlist of image domains.
- Add a Content Security Policy that blocks inline event handlers, so even a slipped attribute does nothing.
- Render chat output in a sandboxed iframe if it must support rich formatting.
Try it, then go further
Reading about it is the easy part. Open the lab, try your own phrasing before peeking at the spoiler, and see how many attempts it takes.
