{"id":"CVE-2026-126206","kind":"vulnerability","aliases":["CVE-2026-126206","GHSA-jcff-cj73-4w4f"],"related":[],"title":"Loofah allowed_uri? treats data:text&sol;html as safe text/plain","description":"## Summary\n\n`Loofah::HTML5::Scrub.allowed_uri?` returns `true` for a `data:` URI whose mediatype is written with the HTML5 named character reference `&sol;` in place of the solidus. Loofah reads that mediatype as malformed and falls back to the allowed `text/plain`, while a browser decodes the reference and reads the same URI as `text/html`.\n\n## Details\n\n`allowed_uri?` decodes HTML entities with `CGI.unescapeHTML`, which handles numeric character references but not HTML5 named character references. `&sol;` therefore survives normalization, so the mediatype carries no `/`, fails the MIME grammar, and falls back to the allowed `text/plain` under the WHATWG rule for a malformed mediatype.\n\n`&colon;`, `&sol;`, `&Tab;` and `&NewLine;` are the only named character references in the HTML5 table that decode to characters that change how a URI parses (U+003A, U+002F, U+0009 and U+000A; there is no named reference for U+000D), so this closes the set.\n\nLoofah's default `sanitize()` path is **not** affected, because Nokogiri decodes the entity during parsing before Loofah evaluates the URI. This issue affects only callers of the public `allowed_uri?` string-level helper that pass it HTML-encoded strings.\n\n## Impact\n\nApplications that validate a user-controlled URL with `Loofah::HTML5::Scrub.allowed_uri?` and then render the approved value into an `href` or another browser-interpreted URI attribute may be vulnerable to cross-site scripting. `Rails::HTML::Sanitizer.allowed_uri?` re-exports the same helper.\n\n## Mitigation\n\nUpgrade to Loofah >= 2.25.3.\n\n## Credit\n\nResponsibly reported by GitHub user @ngpestelos.","summary":"Loofah's allowed_uri? helper accepts a data: URI whose media type is written with the &sol; named entity, treating it as safe text/plain while browsers read it as text/html. An attacker can pass a script-capable data URL through the check and get cross-site scripting when the approved value is rendered into an href.","severity":"medium","cvss":5.3,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"loofah","fixed":["2.25.3"],"affected":"= 2.25.2","ecosystem":"rubygems"}],"ecosystems":["rubygems"],"tags":["cwe-184","cwe-79","loofah","package","rails","ruby"],"not_affected_if":["default-sanitize-only","no-allowed-uri-calls"],"how_to_check":"- Check the Loofah version in Gemfile.lock, e.g. `grep -i loofah Gemfile.lock`, and compare it to the affected version `2.25.2`.\n- Search application code for calls to `Loofah::HTML5::Scrub.allowed_uri?` or `Rails::HTML::Sanitizer.allowed_uri?` on user-supplied URLs.\n- Confirm whether those approved URLs are rendered into href or other browser-interpreted URI attributes.","fix":"- `loofah` `2.25.3` (per the affected range `2.25.2` fixed in `2.25.3`; upgrade to Loofah >= 2.25.3).","status":"open","published_at":"2026-11-12T20:56:30+00:00","first_seen_at":"2026-11-12T20:58:53+00:00","triaged_at":"2026-11-12T21:04:10+00:00","updated_at":"2026-11-12T21:40:31+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-11-12T21:04:10Z","model":"claude-haiku-5-5","output":{"fix":"- `loofah` `2.25.3` (per the affected range `2.25.2` fixed in `2.25.3`; upgrade to Loofah >= 2.25.3).","reach":"internet","title":"Loofah: data URI with named entity bypasses URL allowlist check","impact":"medium","summary":"Loofah's allowed_uri? helper accepts a data: URI whose media type is written with the &sol; named entity, treating it as safe text/plain while browsers read it as text/html. An attacker can pass a script-capable data URL through the check and get cross-site scripting when the approved value is rendered into an href.","audience":"developers","category":"package","nicknames":[],"who_cares":["ruby","rails","loofah"],"confidence":0.85,"fix_status":"available","needs_auth":"no","worst_case":"Attacker runs script in victim's browser via rendered link.","analyst_note":"","how_to_check":"- Check the Loofah version in Gemfile.lock, e.g. `grep -i loofah Gemfile.lock`, and compare it to the affected version `2.25.2`.\n- Search application code for calls to `Loofah::HTML5::Scrub.allowed_uri?` or `Rails::HTML::Sanitizer.allowed_uri?` on user-supplied URLs.\n- Confirm whether those approved URLs are rendered into href or other browser-interpreted URI attributes.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["default-sanitize-only","no-allowed-uri-calls"],"who_is_affected":"Only applications that call the public allowed_uri? helper on user-controlled URLs with HTML-encoded input and then render the result into an href or similar attribute. The default sanitize() path is not affected, according to the advisory, so applications using only sanitize() can skip it.","user_interaction":"yes","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"medium"},"cost_usd":0.000856,"input_tokens":5191,"output_tokens":674,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'+000':217C,226C '+0009':214C '+002':211C '+003':208C '-126206':13A,21A '-184':24B '-2026':12A,20A '-79':26B '2.25.3':326C '4w4f':18A 'accept':36B 'affect':240C,255C 'allow':2A,33B,125C,141C,176C,261C 'anoth':298C 'applic':275C 'approv':81B,292C 'attack':61B 'attribut':303C 'back':122C,173C 'browser':55B,129C,300C 'browser-interpret':299C 'caller':257C 'capabl':67B 'carri':165C 'cgi.unescapehtml':147C 'chang':202C 'charact':107C,151C,157C,191C,200C 'check':72B 'cj73':17A 'close':230C 'control':281C 'credit':327C 'cross':76B,309C 'cross-sit':75B,308C 'cve':11A,19A 'cwe':23B,25B 'd':227C 'data':5A,38B,68B,97C 'decod':130C,143C,198C,243C 'default':235C 'detail':140C 'encod':272C 'entiti':48B,145C,245C 'evalu':250C 'export':318C 'f':212C 'fail':167C 'fall':121C,172C 'get':74B 'ghsa':15A 'ghsa-jcff-cj73-4w4f':14A 'github':331C 'grammar':170C 'handl':149C 'helper':35B,266C,321C 'href':87B,296C 'html':7A,144C,271C,313C 'html-encod':270C 'html5':90C,105C,155C,195C,285C 'impact':274C 'interpret':301C 'issu':254C 'jcff':16A 'level':265C 'loofah':1A,22A,27B,31B,89C,114C,233C,249C,284C,325C 'malform':119C,184C 'may':304C 'media':41B 'mediatyp':100C,117C,164C,185C 'mime':169C 'mitig':322C 'name':47B,106C,156C,190C,222C 'ngpestelo':333C 'nokogiri':242C 'normal':161C 'numer':150C 'packag':28B 'pars':206C,247C 'pass':63B,268C 'path':237C 'place':110C 'public':260C 'rail':29B,312C 're':317C 're-export':316C 'read':56B,115C,134C 'refer':108C,132C,152C,158C,192C,223C 'render':84B,290C 'report':329C 'respons':328C 'return':93C 'rubi':30B 'rule':181C 'safe':9A,52B 'sanit':236C 'sanitizer.allowed':314C 'script':66B,78B,311C 'script-cap':65B 'scrub.allowed':91C,286C 'set':232C 'site':77B,310C 'solidus':113C 'string':264C,273C 'string-level':263C 'summari':88C 'surviv':160C 'tabl':196C 'text':6A 'text/html':59B,139C 'text/plain':10A,53B,126C,177C 'therefor':159C 'treat':4A,49B 'true':94C 'type':42B 'u':207C,210C,213C,216C,225C 'upgrad':323C 'uri':3A,34B,39B,92C,98C,137C,142C,205C,252C,262C,287C,302C,315C 'url':69B,282C 'user':280C,332C 'user-control':279C 'valid':277C 'valu':82B,293C 'vulner':306C 'whatwg':180C 'whose':40B,99C 'written':44B,102C","nicknames":[],"mitigations":[],"who_affected":"Only applications that call the public allowed_uri? helper on user-controlled URLs with HTML-encoded input and then render the result into an href or similar attribute. The default sanitize() path is not affected, according to the advisory, so applications using only sanitize() can skip it.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["loofah"],"ptokens":["loofah"],"labels":["cwe184","cwe79","loofah","medium","package","rails","ruby","rubygems"],"campaign_type":null,"search_a":"'-126206':13A,21A '-2026':12A,20A '4w4f':18A 'allow':2A 'cj73':17A 'cve':11A,19A 'data':5A 'ghsa':15A 'ghsa-jcff-cj73-4w4f':14A 'html':7A 'jcff':16A 'loofah':1A,22A 'safe':9A 'text':6A 'text/plain':10A 'treat':4A 'uri':3A","search_b":"'-184':24B '-79':26B 'accept':36B 'allow':33B 'approv':81B 'attack':61B 'browser':55B 'capabl':67B 'check':72B 'cross':76B 'cross-sit':75B 'cwe':23B,25B 'data':38B,68B 'entiti':48B 'get':74B 'helper':35B 'href':87B 'loofah':27B,31B 'media':41B 'name':47B 'packag':28B 'pass':63B 'rail':29B 'read':56B 'render':84B 'rubi':30B 'safe':52B 'script':66B,78B 'script-cap':65B 'site':77B 'text/html':59B 'text/plain':53B 'treat':49B 'type':42B 'uri':34B,39B 'url':69B 'valu':82B 'whose':40B 'written':44B","search_c":"'+000':217C,226C '+0009':214C '+002':211C '+003':208C '2.25.3':326C 'affect':240C,255C 'allow':125C,141C,176C,261C 'anoth':298C 'applic':275C 'approv':292C 'attribut':303C 'back':122C,173C 'browser':129C,300C 'browser-interpret':299C 'caller':257C 'carri':165C 'cgi.unescapehtml':147C 'chang':202C 'charact':107C,151C,157C,191C,200C 'close':230C 'control':281C 'credit':327C 'cross':309C 'cross-sit':308C 'd':227C 'data':97C 'decod':130C,143C,198C,243C 'default':235C 'detail':140C 'encod':272C 'entiti':145C,245C 'evalu':250C 'export':318C 'f':212C 'fail':167C 'fall':121C,172C 'github':331C 'grammar':170C 'handl':149C 'helper':266C,321C 'href':296C 'html':144C,271C,313C 'html-encod':270C 'html5':90C,105C,155C,195C,285C 'impact':274C 'interpret':301C 'issu':254C 'level':265C 'loofah':89C,114C,233C,249C,284C,325C 'malform':119C,184C 'may':304C 'mediatyp':100C,117C,164C,185C 'mime':169C 'mitig':322C 'name':106C,156C,190C,222C 'ngpestelo':333C 'nokogiri':242C 'normal':161C 'numer':150C 'pars':206C,247C 'pass':268C 'path':237C 'place':110C 'public':260C 'rail':312C 're':317C 're-export':316C 'read':115C,134C 'refer':108C,132C,152C,158C,192C,223C 'render':290C 'report':329C 'respons':328C 'return':93C 'rule':181C 'sanit':236C 'sanitizer.allowed':314C 'script':311C 'scrub.allowed':91C,286C 'set':232C 'site':310C 'solidus':113C 'string':264C,273C 'string-level':263C 'summari':88C 'surviv':160C 'tabl':196C 'text/html':139C 'text/plain':126C,177C 'therefor':159C 'true':94C 'u':207C,210C,213C,216C,225C 'upgrad':323C 'uri':92C,98C,137C,142C,205C,252C,262C,287C,302C,315C 'url':282C 'user':280C,332C 'user-control':279C 'valid':277C 'valu':293C 'vulner':306C 'whatwg':180C 'whose':99C 'written':102C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-02T05:03:52+00:00","kev":null,"cvss_vector":"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","cvss_by":"security-advisories@github.com","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-jcff-cj73-4w4f","url":"https://github.com/advisories/GHSA-jcff-cj73-4w4f","severity":"medium","published_at":"2026-11-12T20:56:30+00:00","modified_at":"2026-11-12T20:56:31+00:00","first_seen_at":"2026-11-12T20:58:53+00:00","fetched_at":"2026-11-12T21:08:21+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-126206","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-126206","severity":null,"published_at":"2026-11-12T21:17:21+00:00","modified_at":"2026-11-12T21:17:21+00:00","first_seen_at":"2026-11-12T21:40:31+00:00","fetched_at":"2026-11-12T21:40:32+00:00","note":null}],"documents":[{"id":138485,"url":"https://api.github.com/repos/flavorjones/loofah/security-advisories/GHSA-jcff-cj73-4w4f","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":138486,"url":"https://github.com/flavorjones/loofah/security/advisories/GHSA-jcff-cj73-4w4f","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":"2026-11-02T05:03:52+00:00","relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":138468,"url":"https://github.com/flavorjones/loofah/pull/324","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":138488,"url":"https://github.com/flavorjones/loofah/commit/ad92002966053309f83b550861e2ef04bc714744","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":137847,"url":"https://github.com/flavorjones/loofah/releases/tag/v2.25.3","title":"Release notes: github.com","kind":"release","source":"nvd","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}