CVE-2026-126206: Cross-Site Scripting via HTML5 Named Solidus Entity in Loofah URI Validator

Vulnerability ID: CVE-2026-126206
CVSS Score: 5.3
Published: 2026-11-12

CVE-2026-126206 (GHSA-jcff-cj73-4w4f) is a Cross-Site Scripting (XSS) vulnerability in version 2.25.2 of the Loofah Ruby gem. The vulnerability stems from an incomplete character reference normalization routine in the Loofah::HTML5::Scrub.allowed_uri? method, which fails to decode the HTML5 named character reference &sol; (representing the solidus character /). Consequently, a crafted data: URI such as data:text&sol;html,<script>alert(1)</script> bypasses mediatype validation by triggering the WHATWG MIME parser fallback to text/plain, while web browsers later resolve &sol; to / and execute the payload as text/html.

TL;DR

Loofah 2.25.2 fails to decode the HTML5 named character reference &sol; (/) inside Loofah::HTML5::Scrub.allowed_uri? (also exposed via Rails::HTML::Sanitizer.allowed_uri?). Attackers can supply data:text&sol;html,<script>... to bypass URI validation as text/plain and achieve Cross-Site Scripting (XSS) when the URI is rendered into an HTML attribute.

⚠️ Exploit Status: POC

Technical Details

  • CVE / Advisory ID: CVE-2026-126206 / GHSA-jcff-cj73-4w4f
  • Vulnerability Class: CWE-79 (Cross-Site Scripting) / CWE-184 (Incomplete List of Disallowed Inputs)
  • CVSS v4.0 Score: 5.3 Medium (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N)
  • Affected Component: Loofah::HTML5::Scrub.allowed_uri? (loofah gem = 2.25.2)
  • Patched Version: loofah >= 2.25.3
  • Attack Vector: Network (Crafted data: URI using / HTML5 entity)
  • Exploit Status: Proof-of-Concept (PoC) Publicly Available
  • CISA KEV Status: Not Listed

Affected Systems

  • loofah Ruby gem (version 2.25.2)
  • rails-html-sanitizer (when invoking Rails::HTML::Sanitizer.allowed_uri? with loofah 2.25.2)
  • loofah: = 2.25.2 (Fixed in: 2.25.3)

Code Analysis

Commit: ad92002

Ensure Loofah::HTML5::Scrub.allowed_uri? recognizes the named character reference / and rejects data: URIs that disguise a disallowed mediatype with it.

diff --git a/lib/loofah/html5/scrub.rb b/lib/loofah/html5/scrub.rb index 2a35090..e8b524a 100644 --- a/lib/loofah/html5/scrub.rb +++ b/lib/loofah/html5/scrub.rb @@ -53,6 +53,15 @@ module Scrub # URIs. CGI.unescapeHTML does not decode these, so they are handled explicitly. WHITESPACE_CHARACTER_REFERENCES = /&(Tab|NewLine);/ + # HTML5 named character references for the delimiters that decide how a URI parses: + # ":" ends the scheme, and "/" separates the type and subtype of a data: mediatype. + # CGI.unescapeHTML does not decode these either. Together with + # WHITESPACE_CHARACTER_REFERENCES these are every named character reference that + # decodes to U+0009, U+000A, U+002F, or U+003A, so no other named reference can + # change how a URI parses. + DELIMITER_CHARACTER_REFERENCES = { "&colon;" => ":", "&sol;" => "/" }.freeze + DELIMITER_CHARACTER_REFERENCE = Regexp.union(DELIMITER_CHARACTER_REFERENCES.keys) + class << self def allowed_element?(element_name) ::Loofah::HTML5::SafeList::ALLOWED_ELEMENTS_WITH_LIBXML2.include?(element_name) @@ -234,12 +243,12 @@ def allowed_uri?(uri_string) # also decode the semicolon-less ones, which browsers still decode and execute. Normalizing # more aggressively than a browser only rejects more, which is safe. Control characters are # stripped both before and after decoding, since decoding can produce them. That strip must - # precede WHITESPACE_CHARACTER_REFERENCES: removing a control character can reveal a named - # whitespace reference. + # precede the named reference substitutions: removing a control character can reveal a + # named reference. uri_string = decode_numeric_character_references(CGI.unescapeHTML(uri_string.gsub(CONTROL_CHARACTERS, ""))) uri_string.gsub!(CONTROL_CHARACTERS, "") uri_string.gsub!(WHITESPACE_CHARACTER_REFERENCES, "") - uri_string.gsub!("&colon;", ":") + uri_string.gsub!(DELIMITER_CHARACTER_REFERENCE, DELIMITER_CHARACTER_REFERENCES) uri_string.downcase! if URI_PROTOCOL_REGEX.match?(uri_string) protocol = uri_string.split(SafeList::PROTOCOL_SEPARATOR)[0] 

Exploit Details

Mitigation Strategies

  • Upgrade the loofah Ruby gem to version 2.25.3 or newer across all environments.
  • Prefer full DOM fragment sanitization (Loofah.scrub_fragment or Rails sanitize helper) over standalone string validation via allowed_uri?.
  • Ensure all user-supplied URI strings rendered into HTML attributes pass through standard attribute escaping helpers (such as Rails link_to or tag.a) rather than raw .html_safe string interpolation.

Remediation Steps:

  1. Audit project Gemfile.lock files using bundle list | grep loofah or bundle exec bundler-audit check --update to identify instances of loofah version 2.25.2.
  2. Search application source code (app/, lib/, config/) for direct invocations of Loofah::HTML5::Scrub.allowed_uri? or Rails::HTML::Sanitizer.allowed_uri?.
  3. Update Gemfile to require gem "loofah", ">= 2.25.3" and execute bundle update loofah.
  4. Run the application test suite and redeploy affected services.

References

Read the full report for CVE-2026-126206 on our website for more details including interactive diagrams and full exploit analysis.