CVE-2026-91121 Overview
CVE-2026-91121 is a stored HTML injection vulnerability [CWE-79] in the Discourse open-source discussion platform. The flaw resides in the chat plugin, where attacker-controlled upload filenames are rendered as unescaped HTML inside chat message excerpts. An authenticated user with permission to upload files and send chat messages can embed markup in a filename. That markup is then interpreted by chat channel lists, chat summary emails, pinned message bars, reply previews, thread previews, and other excerpt renderers.
Critical Impact
Trusted-HTML injection alters rendered excerpt content across multiple chat surfaces. JavaScript execution was not demonstrated under the default Content Security Policy, but sites that disable or relax the default CSP face increased exposure.
Affected Products
- Discourse versions prior to 2026.1.8
- Discourse versions prior to 2026.6.3 and 2026.7.2
- Discourse versions prior to 2026.8.0
Discovery Timeline
- 2026-09-24 - CVE-2026-91121 published to the National Vulnerability Database
- 2026-09-24 - Last updated in NVD database
Technical Details for CVE-2026-91121
Vulnerability Analysis
The vulnerability stems from improper output encoding in the Discourse chat plugin. When a chat message contains only uploads and no cooked body text, the plugin substituted the raw original_filename of the first upload as the message excerpt. Because the excerpt was treated as trusted HTML by downstream renderers, an attacker who crafted a filename containing HTML tags could inject markup into any component that displayed the excerpt.
Affected rendering surfaces include chat channel lists, pinned message bars, reply and thread previews, and chat summary emails. The injection is persistent for the lifetime of the uploaded message and reaches every user who views an affected excerpt.
Root Cause
In plugins/chat/app/models/chat/message.rb, the excerpt and cooked_for_excerpt methods interpolated uploads.first.original_filename directly into HTML output without escaping. The Chat::Thread#excerpt method in plugins/chat/app/models/chat/thread.rb propagated this unsafe value to thread previews. No sanitization or entity encoding was applied to filenames, which users fully control at upload time.
Attack Vector
An authenticated low-privileged user uploads a file whose filename contains HTML markup, then posts the upload to a chat channel. Any surface that renders the message excerpt interprets the injected markup. Exploitation requires network access to the Discourse site, low privileges, and no victim interaction beyond viewing a chat surface that displays the excerpt.
# Patch: plugins/chat/app/models/chat/message.rb
# SECURITY: Escape upload filenames in chat message excerpts
end
# upload-only messages are better represented as the filename
- return uploads.first.original_filename if cooked.blank? && uploads.present?
+ return upload_filename_excerpt if cooked.blank? && uploads.present?
# this may return blank for some complex things like quotes, that is acceptable
PrettyText.excerpt(cooked, EXCERPT_LENGTH, strip_links:, keep_mentions: true)
end
+ def excerpt_for_display
+ return upload_filename_excerpt if only_uploads?
+
+ excerpt || build_excerpt
+ end
+
def cooked_for_excerpt
- (cooked.blank? && uploads.present?) ? "<p>#{uploads.first.original_filename}</p>" : cooked
+ (cooked.blank? && uploads.present?) ? "<p>#{upload_filename_excerpt}</p>" : cooked
end
Source: Discourse Commit 24cd7cc
Detection Methods for CVE-2026-91121
Indicators of Compromise
- Upload records whose original_filename contains HTML control characters such as <, >, ", or complete tag fragments like <img, <svg, or onerror=.
- Chat messages with blank or empty cooked bodies but attached uploads bearing markup-laden filenames.
- Outbound chat summary emails whose excerpt sections contain unexpected HTML elements injected via filenames.
Detection Strategies
- Query the uploads table for filenames matching a regular expression that detects angle brackets, event handler attributes, or script-like substrings.
- Audit chat channel, thread, and pinned-message excerpt fields for stored markup that should have been plain text.
- Review web server and application logs for POST requests to upload endpoints that submit multipart filenames containing HTML metacharacters.
Monitoring Recommendations
- Enable and monitor Content Security Policy violation reports to catch any attempt to escalate the injection toward script execution.
- Alert on new uploads where the stored filename contains HTML tag syntax, especially from recently created or low-reputation accounts.
- Track changes to CSP configuration in Discourse site settings and flag any relaxation of the default policy.
How to Mitigate CVE-2026-91121
Immediate Actions Required
- Upgrade Discourse to version 2026.1.8, 2026.6.3, 2026.7.2, or 2026.8.0 depending on the active release branch.
- Keep the default Content Security Policy enabled; this policy prevented demonstrated JavaScript execution during advisory testing.
- Audit existing chat uploads for malicious filenames and sanitize or remove offending records.
Patch Information
The fix introduces an upload_filename_excerpt helper and a new excerpt_for_display method that escapes upload filenames before they reach any excerpt renderer. Chat::Thread#excerpt now calls original_message.excerpt_for_display to inherit the sanitized output. Review the full patch set in the Discourse Security Advisory GHSA-34rh-wjfv-65gq, Discourse Pull Request #42882, and backport commits 6e7a181 and 9d6f5e6.
Workarounds
- If immediate upgrade is not possible, restrict upload and chat posting privileges to trusted user groups only.
- Verify that the default content_security_policy site setting is enabled and that no custom directives weaken script-src or object-src.
- Implement server-side filename normalization that strips HTML metacharacters from uploaded filenames at ingestion.
# Verify the installed Discourse version and upgrade
cd /var/discourse
./launcher rebuild app
# Confirm version after rebuild
./launcher enter app
rails runner 'puts Discourse::VERSION::STRING'
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.