genai
SECURITY LAB
IncidentsImproper output handling

Discourse AI Triage: Stored XSS from Model Output

Medium severityMarch 2026LLM10: Improper Output HandlingLLM01: Prompt Injection
Status: CVE-2026-27740 (CVSS 5.1); fixed by HTML-escaping all LLM-generated content
On this page

The moderator opened the AI's summary — and the AI's summary 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 5.1 (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 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 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.

Replay this attack
Store model output that executes as script when viewed — the stored-XSS output-handling class.
Open the live lab
Runs as a live, sandboxed lab. Sign-in required — this replay is a Pro lab. Recreates the attack class, not this exact branded bot.