CVE-2026-91134 Overview
CVE-2026-91134 is a stored cross-origin iframe injection in Discourse, an open-source discussion platform. The post sanitizer failed to normalize iframe src values before checking them against the allowed_iframes prefix allowlist. When an attacker embedded encoded userinfo in the URL, the sanitizer validated a decoded form while the browser rendered the attacker-controlled host, bypassing the allowlist. An authenticated user with posting privileges could persist the iframe and cause attacker-controlled cross-origin content to render for other users. The issue is fixed in versions 2026.1.8, 2026.6.3, 2026.7.2, and 2026.8.0.
Critical Impact
Authenticated attackers can persist cross-origin iframes that render attacker-controlled content inside trusted Discourse posts, enabling phishing, clickjacking, and content spoofing against other users.
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 CVE-2026-91134 published to NVD
- 2026-09-29 - Last updated in NVD database
Technical Details for CVE-2026-91134
Vulnerability Analysis
The vulnerability is an input validation flaw [CWE-20] in the Discourse post sanitizer located in frontend/pretty-text/addon/sanitizer.js. The sanitizer parsed each iframe src, decoded it, and tested the decoded form against the configured allowed_iframes prefix patterns. The stored src attribute, however, was emitted to the DOM in its original encoded form. Browsers interpret percent-encoded userinfo (the user@host portion before the authority) and resolve the real host from the encoded value, so the allowlist and the browser disagreed on which host was being loaded. Because posts persist, exploitation produces a stored condition that affects every viewer of the post.
Root Cause
The root cause is inconsistent URL normalization between the validation step and the output step. The sanitizer validated only the decoded URL but preserved the raw encoded URL for rendering. An iframe src such as https://allowed.example.com%40attacker.tld/ decodes to a value that matches the https://allowed.example.com prefix, while the browser parses the %40 as @ and treats attacker.tld as the authority.
Attack Vector
An authenticated user with post-creation privileges submits a post containing an iframe whose src includes encoded userinfo crafted to defeat the prefix check. Once stored, the iframe renders attacker-controlled content for anyone viewing the post.
// Patch: frontend/pretty-text/addon/sanitizer.js
// Validate BOTH the decoded and the raw stored iframe src against allowed_iframes
} catch {
return false;
}
const iframeUrls = [decoded];
iframeUrls.push(value);
return (
!decoded.match(/\/\.+\//) &&
allowedIframes.some((i) => {
const regex = i
// escape regex, keeping *
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
.replace(/\*/g, "[^/]+");
const allowedIframe = new RegExp(`^${regex}.*$`, "i");
return iframeUrls.every((iframeUrl) =>
allowedIframe.test(iframeUrl)
);
})
);
})())
// Source: https://github.com/discourse/discourse/commit/0a8015e6c7a5079981843721494d30c9a91f5415
The fix pushes both the decoded value and the raw stored value into iframeUrls, then requires every form to match an allowed prefix via iframeUrls.every(...). This closes the parser-differential by forcing the validator to agree with what the browser will render.
Detection Methods for CVE-2026-91134
Indicators of Compromise
- Stored posts containing <iframe> tags whose src attribute includes %40, %3A, or other encoded authority characters before the host.
- Iframe URLs that pass prefix inspection but resolve, after browser parsing, to a host outside the configured allowed_iframes list.
- Referrer logs on third-party domains showing unexpected inbound traffic from the Discourse instance's post view pages.
Detection Strategies
- Scan the posts table and rendered cooked HTML for iframe elements, decode each src, and compare the parsed host against the allowed_iframes configuration.
- Enable Content Security Policy (CSP) reporting with a strict frame-src directive to receive violation reports for out-of-policy iframe loads.
- Review audit logs for recently created or edited posts by low-reputation accounts that include raw HTML or oneboxed iframe content.
Monitoring Recommendations
- Monitor egress and DNS telemetry from client browsers rendering Discourse pages for lookups to domains not present in allowed_iframes.
- Alert on new posts whose HTML contains percent-encoded @ (%40) within iframe src values.
- Track Discourse version strings exposed via /srv/status or the admin dashboard to confirm patched builds are deployed across all instances.
How to Mitigate CVE-2026-91134
Immediate Actions Required
- Upgrade Discourse to 2026.1.8, 2026.6.3, 2026.7.2, or 2026.8.0 depending on your release branch.
- Audit existing posts for iframes with encoded userinfo and unpublish or edit any that reference non-allowlisted hosts.
- Review and tighten the allowed_iframes site setting to the minimum set of trusted embed providers.
Patch Information
The fix is implemented in frontend/pretty-text/addon/sanitizer.js and shipped across backport commits 0a8015e, 1304b0c, bac7dd1, and fed3a58. See the GitHub Security Advisory GHSA-54vw-chv3-wjpv and Pull Request #42882 for the complete change set. The patch validates both the decoded and raw iframe src against the allowlist.
Workarounds
- Temporarily clear or minimize the allowed_iframes site setting until the upgrade is applied.
- Restrict post-creation privileges to trusted trust-level groups while the environment is unpatched.
- Deploy a Content Security Policy with a strict frame-src directive that enumerates only required embed hosts, preventing browsers from loading unauthorized iframes.
# Example: enforce a strict frame-src via reverse proxy (nginx)
add_header Content-Security-Policy "frame-src 'self' https://www.youtube.com https://player.vimeo.com; frame-ancestors 'self';" always;
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.