Discourse AI Triage: Stored XSS from Model Output
On this page
The moderator opened the AI's summary — and the AI's summary ran attacker JavaScript in their session.
| When | March 2026 |
|---|---|
| Target | Discourse discourse-ai triage / Review Queue |
| Vendor | Discourse |
| Technique | LLM output rendered as trusted HTML (htmlSafe) |
| CVE | CVE-2026-27740 — CVSS 5.1 (Medium) |
- 1Attacker posts forum content crafted to steer the triage model via injection
- 2The AI triage output (raw HTML) lands in the moderator Review Queue
- 3A moderator opens the item; the script runs in their authenticated session (stored XSS)
What happened
Discourse's AI triage automation passed the raw text returned by an LLM into the moderator Review Queue UI and rendered it with Ember's htmlSafe, treating model output as trusted. An attacker could post forum content that, via prompt injection, made the triage model emit HTML/JavaScript; when a moderator or admin later opened the flagged item, the script executed in their privileged session — stored XSS. CVE-2026-27740; fixed by HTML-escaping all LLM-generated content.
How it worked
The model's output was rendered as HTML rather than escaped text, so attacker-influenced output became executable markup in a trusted admin view.
Root cause
Treating LLM output as safe HTML. Model output is untrusted and must be escaped before rendering — especially in privileged UIs.
What a test would have caught
Feeding injection through the triaged content and checking whether the rendered review item can execute script surfaces the stored-XSS path.
How to prevent it
- HTML-escape model output before it reaches any template.
- Never render LLM output with a “trusted HTML” helper.
- Apply the strongest care in admin/privileged views.
Feel it yourselfThe replay lab stores model output that executes as script when it's viewed — the stored-XSS output-handling class.
FAQ
How did the XSS get stored?
The AI triage step passed the model's raw text into the Review Queue UI and rendered it as trusted HTML. Attacker-controlled forum content, via injection, made the model emit script that ran when staff opened the flagged item.
Why is an admin-view XSS serious?
Because it executes in a privileged moderator/admin session — the highest-value context in the app.
What was the fix?
HTML-escape all LLM-generated content before it reaches templates (ERB::Util.html_escape), i.e. treat model output as untrusted data, not safe HTML.
Sources
- Discourse Stored XSS in AI Triage Automation (GHSA-95hc-42c6-wvvr) — GitHub Security Advisories
- CVE-2026-27740 — OSV.dev