Securing Model Output: XSS, Links, and Markdown
Treat model output as untrusted input to your UI: sanitizing HTML and markdown, link and image exfiltration, and safe rendering of generated code.
Deskmate's help chatbot renders markdown, because answers with lists and links read better. One day a help-center article picks up a malicious public comment, and the next customer who asks about billing gets an answer ending in an image: a markdown image whose URL points at an attacker's server, with a summary of the customer's conversation packed into the query string. The image is one transparent pixel. The browser fetched it automatically, sending that data to the attacker, and nobody clicked anything.
The root mistake is treating model output as trusted because your own code produced it. Anyone who can influence the model's input, whether a user typing or an attacker planting text, can influence its output. In this lesson you'll render model output the way you'd render any untrusted user content: safe markdown and HTML, links and images that can't leak data, sanitized output for other destinations like email and SQL, sandboxed previews of generated code, and a Content Security Policy as a backstop.
Model output is untrusted input
Every place model output flows is a sink: a destination that interprets it. HTML in the browser is a sink. So are markdown renderers, email clients, SQL engines, shells, file systems, URL fetchers, and the next prompt in a chain. The attacks are the familiar web-security ones; the only new part is that the payload arrives via the model instead of via a form field. That's why the OWASP Top 10 for LLM Applications lists "improper output handling" as its own risk, separate from prompt injection.
| Sink | What goes wrong | The defense |
|---|---|---|
| Browser HTML | Script injection (XSS) | Render as React elements, or sanitize with an allowlist |
| Markdown images | Data leaks through automatic image requests | Don't render model-chosen image URLs; allowlist hosts |
| Markdown links | Phishing, data in query strings, javascript: URLs | Allowlist hosts, show real destinations, block dangerous schemes |
| Outgoing email | Same as above, in a client whose rules you don't control | Sanitize on the server before sending |
| SQL | Destructive or unauthorized queries | Parameters, read-only roles, validated filters |
Shell or eval | Arbitrary code execution | Don't. If code must run, sandbox it |
| Server-side fetch | Requests to internal services (SSRF) | Host allowlist, block private network addresses |
| The next prompt | Injection carried from one step to the next | Treat earlier output as untrusted data |
The rest of this lesson works through the ones that bite product teams most often.