CVE-2026-72720 Overview
CVE-2026-72720 is an HTML injection vulnerability in Discourse, an open-source discussion platform. The flaw resides in PrettyText.format_for_email where cooked attribute values are reparsed as markup. Attackers can craft Vimeo iframe sources, secure-upload URLs or dimensions, and hashtag data-slug values so that decoded attribute text is reinterpreted as HTML [CWE-79]. The vulnerable conversion also fails to strictly validate the Vimeo iframe host and path, allowing non-Vimeo iframes to bypass the allowlist. The issue affects Discourse prior to versions 2026.1.7, 2026.6.2, 2026.7.1, and 2026.8.0-latest.1.
Critical Impact
Authenticated attackers can inject HTML into rendered email content and route non-allowlisted iframes through the Vimeo conversion path, enabling stored cross-site scripting against forum readers.
Affected Products
- Discourse versions prior to 2026.1.7 (stable branch)
- Discourse versions prior to 2026.6.2 and 2026.7.1 (beta branches)
- Discourse versions prior to 2026.8.0-latest.1 (tests-passed branch)
Discovery Timeline
- 2026-08-10 - CVE-2026-72720 published to NVD
- 2026-08-10 - Last updated in NVD database
Technical Details for CVE-2026-72720
Vulnerability Analysis
The vulnerability affects PrettyText.format_for_email, the Discourse component that converts "cooked" post HTML into email-safe markup. During conversion, attribute values already stored in the cooked HTML are decoded and then reparsed by the markup pipeline. This double-parsing step allows content sitting inside attributes to be reinterpreted as new HTML nodes.
Three specific attribute surfaces are exploitable: Vimeo iframe src attributes, secure-upload URLs and dimension attributes on images, and hashtag data-slug values. An authenticated user who can post content can embed payloads in these attributes, and the reparse turns the decoded text into live markup when the post is rendered in an email digest or notification.
A second flaw compounds the impact. The Vimeo conversion path did not strictly validate the iframe host or URL path. Any iframe that traversed this code path could be preserved in the output, effectively bypassing the iframe allowlist that would otherwise restrict embeds to Vimeo.
Root Cause
The root cause is unsafe reparsing of attribute values that were already treated as data. PrettyText.format_for_email decodes attribute text and passes it back through a markup parser without re-sanitization. Combined with permissive host matching for Vimeo iframes, this violates the invariant that attribute content must not be reinterpreted as HTML structure.
Attack Vector
Exploitation requires an authenticated Discourse account capable of posting content. The attacker crafts a post containing a Vimeo iframe, secure-upload image, or hashtag with a malicious attribute payload. When the post is emailed to subscribers or rendered through the affected pipeline, the injected HTML executes in the recipient's context, and attacker-controlled iframes bypass the Vimeo allowlist.
# Patch excerpt from lib/pretty_text.rb introducing strict Vimeo validation
BLOCKED_HOTLINKED_SRC_ATTR = "data-blocked-hotlinked-src"
BLOCKED_HOTLINKED_SRCSET_ATTR = "data-blocked-hotlinked-srcset"
+ VIMEO_PLAYER_HOST = "player.vimeo.com"
+ VIMEO_PLAYER_PATH = %r{\A/video/(?<id>\d+)/?\z}
+ VIMEO_UNLISTED_HASH = /\A[a-zA-Z0-9]+\z/
+
@mutex = Mutex.new
@ctx_init = Mutex.new
Source: GitHub Commit 234ee88. The patch introduces explicit constants that constrain the Vimeo iframe host to player.vimeo.com and the path to /video/<numeric id>, closing the allowlist bypass.
Detection Methods for CVE-2026-72720
Indicators of Compromise
- Outbound Discourse emails containing iframes with hosts other than player.vimeo.com or paths not matching /video/<id>.
- Cooked post HTML where attribute values (src, data-slug, image dimensions) contain encoded angle brackets, quote characters, or on* event handler strings.
- Unusual iframe embeds referencing arbitrary domains in posts made by low-reputation or newly registered accounts.
- Email digest recipients reporting unexpected redirects or content rendered from Discourse notifications.
Detection Strategies
- Audit stored posts for iframe elements whose src attribute does not conform to the player.vimeo.com/video/<digits> pattern.
- Scan data-slug, data-orig-src, and image dimension attributes in the posts table for HTML metacharacters that survive decoding.
- Compare pre- and post-patch output of PrettyText.format_for_email on suspicious posts to identify reintroduced markup.
Monitoring Recommendations
- Enable web application firewall logging on POST requests to /posts and /drafts endpoints and inspect payloads for attribute smuggling patterns.
- Log and review any change in email templates or outbound message content size that deviates from baseline.
- Track authenticated user actions correlated with iframe or upload usage in the Discourse admin logs.
How to Mitigate CVE-2026-72720
Immediate Actions Required
- Upgrade Discourse to 2026.1.7, 2026.6.2, 2026.7.1, or 2026.8.0-latest.1 depending on your release track.
- Restrict posting privileges to trusted trust-level users until the patch is applied.
- Review recent posts containing Vimeo iframes, secure uploads, or hashtags for injected payloads and unpublish affected content.
Patch Information
Discourse fixed the vulnerability in versions 2026.1.7, 2026.6.2, 2026.7.1, and 2026.8.0-latest.1. The remediation is described in the GitHub Security Advisory GHSA-3q6f-6gm9-m35v and implemented across pull requests #42244, #42245, #42246, and #42247. The fixes add strict host and path validation for Vimeo iframes and prevent attribute values from being reinterpreted as markup during email conversion.
Workarounds
- Disable the Vimeo onebox and iframe embedding in Discourse site settings until the upgrade is deployed.
- Temporarily suspend outbound email digests and notifications that rely on PrettyText.format_for_email.
- Elevate the minimum trust level required to post links, images, and iframes.
# Verify the running Discourse version inside the container
cd /var/discourse
./launcher enter app
rails runner 'puts Discourse::VERSION::STRING'
# Rebuild after updating to a patched version
exit
git pull
./launcher rebuild app
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

