{"id":"CVE-2026-126209","kind":"vulnerability","aliases":["CVE-2026-126209","GHSA-p9wj-w8xq-vjx6"],"related":[],"title":"Loofah allowed_uri? treats javascript&#x3a alert(1) as safe","description":"## Summary\n\n`Loofah::HTML5::Scrub.allowed_uri?` returns `true` for a `javascript:` or `vbscript:` URI whose scheme is split by a numeric character reference without a trailing semicolon, followed by a character that `allowed_uri?` strips, such as a space or tab. A browser ends the reference at that character and reads `javascript&#x3a alert(1)` as `javascript: alert(1)`.\n\n## Details\n\n`allowed_uri?` strips control characters and whitespace before it decodes numeric character references. Removing the character that ends a semicolon-less reference lets the reference run on into the following text, so `&#x3a alert` decodes as `&#x3aa` (U+03AA) instead of a colon, and no scheme is detected. The same applies to decimal references and to references that encode a scheme letter, as in `&#x6a\\tavascript:alert(1)`.\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 @manus-pi.","summary":"Loofah's allowed_uri? helper accepts javascript: and vbscript: URLs when the scheme is hidden in a numeric character reference without a semicolon, followed by a space or tab. If an application renders that approved value into an href, a browser runs the script, which is cross-site scripting.","severity":"medium","cvss":5.3,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"loofah","fixed":["2.25.3"],"affected":">= 2.25.0, < 2.25.3","ecosystem":"rubygems"}],"ecosystems":["rubygems"],"tags":["cwe-184","cwe-79","loofah","package","rails","ruby","web-apps"],"not_affected_if":["default-sanitize-only","no-allowed_uri-calls"],"how_to_check":"- Search your Gemfile.lock for `loofah` and note the version.\n- Search your code for calls to `allowed_uri?` (Loofah::HTML5::Scrub or Rails::HTML::Sanitizer) whose results are placed in href or other URI attributes.\n- Default `sanitize()` use is not affected according to the advisory.","fix":"- Loofah `2.25.3` (affected range `>= 2.25.0, < 2.25.3`)","status":"open","published_at":"2026-11-12T21:17:22+00:00","first_seen_at":"2026-11-12T21:29:46+00:00","triaged_at":"2026-11-12T21:31:48+00:00","updated_at":"2026-11-12T21:40:31+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-12T21:31:48Z","model":"claude-haiku-5-5","output":{"fix":"- Loofah `2.25.3` (affected range `>= 2.25.0, < 2.25.3`)","reach":"internet","title":"Loofah: URI check lets split entity javascript link pass as safe","impact":"medium","summary":"Loofah's allowed_uri? helper accepts javascript: and vbscript: URLs when the scheme is hidden in a numeric character reference without a semicolon, followed by a space or tab. If an application renders that approved value into an href, a browser runs the script, which is cross-site scripting.","audience":"developers","category":"package","nicknames":[],"who_cares":["rails","ruby","loofah","web-apps"],"confidence":0.8,"fix_status":"available","needs_auth":"no","worst_case":"attacker runs script in victim's browser session via link","analyst_note":"","how_to_check":"- Search your Gemfile.lock for `loofah` and note the version.\n- Search your code for calls to `allowed_uri?` (Loofah::HTML5::Scrub or Rails::HTML::Sanitizer) whose results are placed in href or other URI attributes.\n- Default `sanitize()` use is not affected according to the advisory.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["default-sanitize-only","no-allowed_uri-calls"],"who_is_affected":"Applications that call Loofah::HTML5::Scrub.allowed_uri? (or Rails::HTML::Sanitizer.allowed_uri?) on user-supplied URLs and then output the result into a link or other browser-interpreted URI attribute. The advisory says Loofah's default sanitize() path is not affected, so apps that only use sanitize() can skip it.","user_interaction":"yes","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"medium"},"cost_usd":0.000832,"input_tokens":5143,"output_tokens":635,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'+03':182C '-126209':13A,21A '-184':24B '-2026':12A,20A '-79':26B '1':8A,137C,141C,212C '2.25.3':306C 'aa':183C 'accept':39B 'affect':220C,235C 'alert':7A,136C,140C,177C,211C 'allow':2A,36B,115C,143C,241C 'anoth':278C 'app':33B 'appli':195C 'applic':65B,255C 'approv':68B,272C 'attribut':283C 'browser':74B,125C,280C 'browser-interpret':279C 'caller':237C 'charact':52B,104C,113C,131C,147C,154C,158C 'colon':187C 'control':146C,261C 'credit':307C 'cross':81B,289C 'cross-sit':80B,288C 'cve':11A,19A 'cwe':23B,25B 'decim':197C 'decod':152C,178C,223C 'default':215C 'detail':142C 'detect':192C 'encod':203C,252C 'end':126C,160C 'entiti':225C 'evalu':230C 'export':298C 'follow':57B,110C,173C 'ghsa':15A 'ghsa-p9wj-w8xq-vjx6':14A 'github':311C 'helper':38B,246C,301C 'hidden':48B 'href':72B,276C 'html':251C,293C 'html-encod':250C 'html5':86C,265C 'impact':254C 'instead':184C 'interpret':281C 'issu':234C 'javascript':5A,40B,93C,134C,139C 'less':164C 'let':166C 'letter':206C 'level':245C 'loofah':1A,22A,27B,34B,85C,213C,229C,264C,305C 'manus':314C 'manus-pi':313C 'may':284C 'mitig':302C 'nokogiri':222C 'numer':51B,103C,153C 'p9wj':16A 'packag':28B 'pars':227C 'pass':248C 'path':217C 'pi':315C 'public':240C 'rail':29B,292C 're':297C 're-export':296C 'read':133C 'refer':53B,105C,128C,155C,165C,168C,198C,201C 'remov':156C 'render':66B,270C 'report':309C 'respons':308C 'return':89C 'rubi':30B 'run':75B,169C 'safe':10A 'sanit':216C 'sanitizer.allowed':294C 'scheme':46B,98C,190C,205C 'script':77B,83B,291C 'scrub.allowed':87C,266C 'semicolon':56B,109C,163C 'semicolon-less':162C 'site':82B,290C 'space':60B,121C 'split':100C 'string':244C,253C 'string-level':243C 'strip':117C,145C 'summari':84C 'tab':62B,123C 'tavascript':210C 'text':174C 'trail':108C 'treat':4A 'true':90C 'u':181C 'upgrad':303C 'uri':3A,37B,88C,96C,116C,144C,232C,242C,267C,282C,295C 'url':43B,262C 'user':260C,312C 'user-control':259C 'valid':257C 'valu':69B,273C 'vbscript':42B,95C 'vjx6':18A 'vulner':286C 'w8xq':17A 'web':32B 'web-app':31B 'whitespac':149C 'whose':97C 'without':54B,106C 'x3a':6A,135C,176C 'x3aa':180C 'x6a':209C","nicknames":[],"mitigations":[],"who_affected":"Applications that call Loofah::HTML5::Scrub.allowed_uri? (or Rails::HTML::Sanitizer.allowed_uri?) on user-supplied URLs and then output the result into a link or other browser-interpreted URI attribute. The advisory says Loofah's default sanitize() path is not affected, so apps that only use 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","webapps"],"campaign_type":null,"search_a":"'-126209':13A,21A '-2026':12A,20A '1':8A 'alert':7A 'allow':2A 'cve':11A,19A 'ghsa':15A 'ghsa-p9wj-w8xq-vjx6':14A 'javascript':5A 'loofah':1A,22A 'p9wj':16A 'safe':10A 'treat':4A 'uri':3A 'vjx6':18A 'w8xq':17A 'x3a':6A","search_b":"'-184':24B '-79':26B 'accept':39B 'allow':36B 'app':33B 'applic':65B 'approv':68B 'browser':74B 'charact':52B 'cross':81B 'cross-sit':80B 'cwe':23B,25B 'follow':57B 'helper':38B 'hidden':48B 'href':72B 'javascript':40B 'loofah':27B,34B 'numer':51B 'packag':28B 'rail':29B 'refer':53B 'render':66B 'rubi':30B 'run':75B 'scheme':46B 'script':77B,83B 'semicolon':56B 'site':82B 'space':60B 'tab':62B 'uri':37B 'url':43B 'valu':69B 'vbscript':42B 'web':32B 'web-app':31B 'without':54B","search_c":"'+03':182C '1':137C,141C,212C '2.25.3':306C 'aa':183C 'affect':220C,235C 'alert':136C,140C,177C,211C 'allow':115C,143C,241C 'anoth':278C 'appli':195C 'applic':255C 'approv':272C 'attribut':283C 'browser':125C,280C 'browser-interpret':279C 'caller':237C 'charact':104C,113C,131C,147C,154C,158C 'colon':187C 'control':146C,261C 'credit':307C 'cross':289C 'cross-sit':288C 'decim':197C 'decod':152C,178C,223C 'default':215C 'detail':142C 'detect':192C 'encod':203C,252C 'end':126C,160C 'entiti':225C 'evalu':230C 'export':298C 'follow':110C,173C 'github':311C 'helper':246C,301C 'href':276C 'html':251C,293C 'html-encod':250C 'html5':86C,265C 'impact':254C 'instead':184C 'interpret':281C 'issu':234C 'javascript':93C,134C,139C 'less':164C 'let':166C 'letter':206C 'level':245C 'loofah':85C,213C,229C,264C,305C 'manus':314C 'manus-pi':313C 'may':284C 'mitig':302C 'nokogiri':222C 'numer':103C,153C 'pars':227C 'pass':248C 'path':217C 'pi':315C 'public':240C 'rail':292C 're':297C 're-export':296C 'read':133C 'refer':105C,128C,155C,165C,168C,198C,201C 'remov':156C 'render':270C 'report':309C 'respons':308C 'return':89C 'run':169C 'sanit':216C 'sanitizer.allowed':294C 'scheme':98C,190C,205C 'script':291C 'scrub.allowed':87C,266C 'semicolon':109C,163C 'semicolon-less':162C 'site':290C 'space':121C 'split':100C 'string':244C,253C 'string-level':243C 'strip':117C,145C 'summari':84C 'tab':123C 'tavascript':210C 'text':174C 'trail':108C 'true':90C 'u':181C 'upgrad':303C 'uri':88C,96C,116C,144C,232C,242C,267C,282C,295C 'url':262C 'user':260C,312C 'user-control':259C 'valid':257C 'valu':273C 'vbscript':95C 'vulner':286C 'whitespac':149C 'whose':97C 'without':106C 'x3a':135C,176C 'x3aa':180C 'x6a':209C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-02T05:03:30+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-p9wj-w8xq-vjx6","url":"https://github.com/advisories/GHSA-p9wj-w8xq-vjx6","severity":"medium","published_at":"2026-11-12T21:26:25+00:00","modified_at":"2026-11-12T21:27:46+00:00","first_seen_at":"2026-11-12T21:29:46+00:00","fetched_at":"2026-11-12T21:38:38+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-126209","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-126209","severity":null,"published_at":"2026-11-12T21:17:22+00:00","modified_at":"2026-11-12T21:17:22+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":139205,"url":"https://api.github.com/repos/flavorjones/loofah/security-advisories/GHSA-p9wj-w8xq-vjx6","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":139206,"url":"https://github.com/flavorjones/loofah/security/advisories/GHSA-p9wj-w8xq-vjx6","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":"2026-11-02T05:03:30+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":139208,"url":"https://github.com/flavorjones/loofah/commit/62c04841e8252a1b5373afcd2b89fb9b8279e317","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"}