genai
SECURITY LAB
IncidentsImproper output handling

Discourse AI Triage: Stored XSS from Model Output

Medium severityVendor vulnerabilityMarch 2026LLM10: Improper Output HandlingLLM01: Prompt Injection
Status: CVE-2026-27740 (CVSS 4.0: 5.1 per GitHub; CVSS 3.1: 6.1 per NVD); fixed by HTML-escaping the LLM response in flag reasons
Sources checked October 2026
On this page

The moderator opened the AI's flag — and the flag reason the AI wrote ran attacker JavaScript in their session.

WhenMarch 2026
TargetDiscourse discourse-ai triage / Review Queue
VendorDiscourse
TechniqueLLM output rendered as trusted HTML (htmlSafe)
CVECVE-2026-27740 — CVSS 4.0: 5.1 (GitHub); CVSS 3.1: 6.1 (NVD); Medium
Attack flow
  1. 1Attacker posts forum content crafted to steer the triage model via injection
  2. 2The AI triage output (raw HTML) lands in a flag reason in the moderator Review Queue
  3. 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 flag reasons shown in 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 (CVSS 4.0 score 5.1 from GitHub; NVD scores it 6.1 under CVSS 3.1); fixed by HTML-escaping the LLM response before it is written into the flag reason.

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 flag reason shown in 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 the model's response (and the automation name) with ERB::Util.html_escape before it is written into the flag reason, i.e. treat model output as untrusted data, not safe HTML.

Replay this attack
File a report whose AI-drafted digest carries live markup into a moderator console's raw-HTML render — the stored-XSS path in Discourse's AI triage.
Open the live lab
A live, sandboxed lab. It recreates the technique, not this exact product.