Lessons · LLM10: Improper Output Handling · error
HTML inside a model reply goes into the page as live HTML, so it can run scripts.
Caught by model-output-rendered-as-markdown-without-sanitization · CWE-79, CWE-80
Markdown renderers escape HTML by default. If a reply contains <script>, it is shown as those characters instead of being run. That escaping is the protection.
This code turns the protection off: rehype-raw with no sanitizer, allowDangerousHtml, marked with sanitize: false, or markdown-it with html: true. Whatever the reply contains becomes live HTML in the visitor's browser.
Model replies count as untrusted text. A model can be asked to include HTML, and it will.
Your chat UI renders the model's markdown with raw HTML turned on, because tables looked broken without it.
<ReactMarkdown rehypePlugins={[rehypeRaw]}>
{message.text}
</ReactMarkdown>
The cost: Account takeover for anyone who views the message, and the attacker can do anything the user could do in your app.
Paste this into Claude Code, Cursor, or whatever wrote the code. When llm-audit finds the mistake in your project, npx llm-audit prompt gives you the same prompt with every file and line filled in.
Fix this in my project: model output is rendered as markdown with raw HTML allowed and no sanitizer.
Please:
1. Turn raw HTML off in the markdown renderer.
2. If some HTML is truly needed, add rehype-sanitize after rehype-raw, with an allowlist of only the tags I use.
3. Do not use dangerouslySetInnerHTML or innerHTML for anything that came from a user or a model.
Show me the diff and tell me which tags the allowlist keeps, if any.A prompt that looks for the same mistake everywhere else, without changing anything yet.
Check this project for places that put untrusted text into a page as raw HTML, before I ship it.
Look for:
- dangerouslySetInnerHTML, innerHTML, outerHTML, insertAdjacentHTML, or document.write with anything that is not a fixed string.
- Markdown rendering with raw HTML turned on (rehype-raw without rehype-sanitize, allowDangerousHtml, markdown-it with html: true).
- Server responses built from request data with res.send or res.write.
For each one, give me the file and line, say where the text comes from (user, database, or AI model), and show a payload that would run a script. Do not change any code yet.npx llm-audit scan
Runs on your machine with Semgrep. Nothing is uploaded.