{"id":"CVE-2026-126207","kind":"vulnerability","aliases":["CVE-2026-126207","GHSA-m3x9-938q-5767"],"related":[],"title":"Loofah: CSS url() sanitization bypass via escaped parentheses","description":"## Summary\n\n`Loofah::HTML5::Scrub.scrub_css` rejected any property whose top-level Crass tokens included a `:url` or `:bad_url`, and left `url` out of the allowed CSS functions. Both checks ran only against the first parse of the `style` attribute. When `scrub_css` rebuilt each property it emitted identifiers with their CSS escapes decoded, so parentheses written as escapes — `url\\28 …\\29` — hid the `url` token from the checks, and the rebuilt value was re-parsed into a live `url()` that nothing checked again. An escaped property name, six-digit escapes, escaped comments and escaped quotes all reached the same place.\n\n## Impact\n\nA sanitized `style` attribute could make a viewer's browser issue a request to an attacker-chosen URL. `list-style` fetches on render with no interaction, `cursor` on hover. That leaks the viewer's IP address, user agent and access timing, and can reach hosts routable only from the viewer's network. No script survives sanitization and the attacker cannot read the response, so the request itself is the whole impact.\n\nApplications that sanitize user-supplied HTML with Loofah's default allowlist and display it to other people are affected.\n\n## Mitigation\n\nUpgrade to Loofah >= 2.25.3.\n\n## Credit\n\nReported by NotAFlightRisk through GitHub's private vulnerability reporting.","summary":"Loofah's CSS sanitizer missed url() calls written with CSS escapes, so a sanitized style attribute can still make a viewer's browser request an attacker-chosen URL. The request leaks the viewer's IP address, user agent and access timing, but no script runs and the response is not readable.","severity":"medium","cvss":4.7,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"loofah","fixed":["2.25.3"],"affected":"< 2.25.3","ecosystem":"rubygems"}],"ecosystems":["rubygems"],"tags":["cwe-180","cwe-184","loofah","package","rails","ruby","web-apps"],"not_affected_if":["no-user-html","not-displayed-to-others"],"how_to_check":"- Run `bundle list loofah` or check `Gemfile.lock` for the `loofah` version\n- Search your code for `Loofah::HTML5::Scrub` or `scrub_css` to see whether user-supplied HTML is sanitized and shown to other users","fix":"- loofah (rubygems): upgrade to `2.25.3` or later","status":"open","published_at":"2026-11-12T21:05:41+00:00","first_seen_at":"2026-11-12T21:09:02+00:00","triaged_at":"2026-11-12T21:10:54+00:00","updated_at":"2026-11-12T21:10:54+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-12T21:10:54Z","model":"claude-haiku-5-5","output":{"fix":"- loofah (rubygems): upgrade to `2.25.3` or later","reach":"internet","title":"Loofah: escaped CSS parentheses let sanitized HTML trigger outbound browser requests","impact":"low","summary":"Loofah's CSS sanitizer missed url() calls written with CSS escapes, so a sanitized style attribute can still make a viewer's browser request an attacker-chosen URL. The request leaks the viewer's IP address, user agent and access timing, but no script runs and the response is not readable.","audience":"developers","category":"package","nicknames":[],"who_cares":["ruby","rails","loofah","web-apps"],"confidence":0.8,"fix_status":"available","needs_auth":"unknown","worst_case":"Viewer's browser sends requests to attacker-chosen URLs, leaking IP address","analyst_note":"Product fields (product_name, product_ecosystem) are omitted because the input lists products.","how_to_check":"- Run `bundle list loofah` or check `Gemfile.lock` for the `loofah` version\n- Search your code for `Loofah::HTML5::Scrub` or `scrub_css` to see whether user-supplied HTML is sanitized and shown to other users","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["no-user-html","not-displayed-to-others"],"who_is_affected":"Applications that sanitize user-supplied HTML with Loofah's default allowlist and display it to other people are affected. Apps that do not render sanitized user HTML, or that only handle trusted content, are less exposed. The sources do not say whether other sanitizer configurations are affected.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"no","severity_if_unknown":"medium"},"cost_usd":0.000802,"input_tokens":5035,"output_tokens":597,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-126207':11A,19A '-180':22B '-184':24B '-2026':10A,18A '2.25.3':286C '28':145C '29':146C '5767':16A '938q':15A 'access':72B,230C 'address':68B,226C 'affect':281C 'agent':70B,228C 'allow':110C 'allowlist':273C 'app':31B 'applic':262C 'attack':58B,205C,249C 'attacker-chosen':57B,204C 'attribut':47B,124C,192C 'bad':102C 'browser':54B,198C 'bypass':5A 'call':38B 'cannot':250C 'check':114C,153C,168C 'chosen':59B,206C 'comment':179C 'could':193C 'crass':96C 'credit':287C 'css':2A,34B,41B,88C,111C,127C,136C 'cursor':217C 'cve':9A,17A 'cwe':21B,23B 'decod':138C 'default':272C 'digit':176C 'display':275C 'emit':132C 'escap':7A,42B,137C,143C,171C,177C,178C,181C 'fetch':211C 'first':119C 'function':112C 'ghsa':13A 'ghsa-m3x9-938q':12A 'github':292C 'hid':147C 'host':235C 'hover':219C 'html':268C 'html5':86C 'identifi':133C 'impact':188C,261C 'includ':98C 'interact':216C 'ip':67B,225C 'issu':199C 'leak':63B,221C 'left':105C 'level':95C 'list':209C 'list-styl':208C 'live':164C 'loofah':1A,20A,25B,32B,85C,270C,285C 'm3x9':14A 'make':50B,194C 'miss':36B 'mitig':282C 'name':173C 'network':242C 'notaflightrisk':290C 'noth':167C 'packag':26B 'parenthes':8A,140C 'pars':120C,161C 'peopl':279C 'place':187C 'privat':294C 'properti':91C,130C,172C 'quot':182C 'rail':27B 'ran':115C 're':160C 're-pars':159C 'reach':184C,234C 'read':251C 'readabl':83B 'rebuilt':128C,156C 'reject':89C 'render':213C 'report':288C,296C 'request':55B,62B,201C,256C 'respons':80B,253C 'routabl':236C 'rubi':28B 'run':77B 'sanit':4A,35B,45B,190C,246C,264C 'script':76B,244C 'scrub':126C 'scrub.scrub':87C 'six':175C 'six-digit':174C 'still':49B 'style':46B,123C,191C,210C 'summari':84C 'suppli':267C 'surviv':245C 'time':73B,231C 'token':97C,150C 'top':94C 'top-level':93C 'upgrad':283C 'url':3A,37B,60B,100C,103C,106C,144C,149C,165C,207C 'user':69B,227C,266C 'user-suppli':265C 'valu':157C 'via':6A 'viewer':52B,65B,196C,223C,240C 'vulner':295C 'web':30B 'web-app':29B 'whole':260C 'whose':92C 'written':39B,141C","nicknames":[],"mitigations":[],"who_affected":"Applications that sanitize user-supplied HTML with Loofah's default allowlist and display it to other people are affected. Apps that do not render sanitized user HTML, or that only handle trusted content, are less exposed. The sources do not say whether other sanitizer configurations are affected.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["loofah"],"ptokens":["loofah"],"labels":["cwe180","cwe184","loofah","medium","package","rails","ruby","rubygems","webapps"],"campaign_type":null,"search_a":"'-126207':11A,19A '-2026':10A,18A '5767':16A '938q':15A 'bypass':5A 'css':2A 'cve':9A,17A 'escap':7A 'ghsa':13A 'ghsa-m3x9-938q':12A 'loofah':1A,20A 'm3x9':14A 'parenthes':8A 'sanit':4A 'url':3A 'via':6A","search_b":"'-180':22B '-184':24B 'access':72B 'address':68B 'agent':70B 'app':31B 'attack':58B 'attacker-chosen':57B 'attribut':47B 'browser':54B 'call':38B 'chosen':59B 'css':34B,41B 'cwe':21B,23B 'escap':42B 'ip':67B 'leak':63B 'loofah':25B,32B 'make':50B 'miss':36B 'packag':26B 'rail':27B 'readabl':83B 'request':55B,62B 'respons':80B 'rubi':28B 'run':77B 'sanit':35B,45B 'script':76B 'still':49B 'style':46B 'time':73B 'url':37B,60B 'user':69B 'viewer':52B,65B 'web':30B 'web-app':29B 'written':39B","search_c":"'2.25.3':286C '28':145C '29':146C 'access':230C 'address':226C 'affect':281C 'agent':228C 'allow':110C 'allowlist':273C 'applic':262C 'attack':205C,249C 'attacker-chosen':204C 'attribut':124C,192C 'bad':102C 'browser':198C 'cannot':250C 'check':114C,153C,168C 'chosen':206C 'comment':179C 'could':193C 'crass':96C 'credit':287C 'css':88C,111C,127C,136C 'cursor':217C 'decod':138C 'default':272C 'digit':176C 'display':275C 'emit':132C 'escap':137C,143C,171C,177C,178C,181C 'fetch':211C 'first':119C 'function':112C 'github':292C 'hid':147C 'host':235C 'hover':219C 'html':268C 'html5':86C 'identifi':133C 'impact':188C,261C 'includ':98C 'interact':216C 'ip':225C 'issu':199C 'leak':221C 'left':105C 'level':95C 'list':209C 'list-styl':208C 'live':164C 'loofah':85C,270C,285C 'make':194C 'mitig':282C 'name':173C 'network':242C 'notaflightrisk':290C 'noth':167C 'parenthes':140C 'pars':120C,161C 'peopl':279C 'place':187C 'privat':294C 'properti':91C,130C,172C 'quot':182C 'ran':115C 're':160C 're-pars':159C 'reach':184C,234C 'read':251C 'rebuilt':128C,156C 'reject':89C 'render':213C 'report':288C,296C 'request':201C,256C 'respons':253C 'routabl':236C 'sanit':190C,246C,264C 'script':244C 'scrub':126C 'scrub.scrub':87C 'six':175C 'six-digit':174C 'style':123C,191C,210C 'summari':84C 'suppli':267C 'surviv':245C 'time':231C 'token':97C,150C 'top':94C 'top-level':93C 'upgrad':283C 'url':100C,103C,106C,144C,149C,165C,207C 'user':227C,266C 'user-suppli':265C 'valu':157C 'viewer':196C,223C,240C 'vulner':295C 'whole':260C 'whose':92C 'written':141C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-02T05:03:07+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-m3x9-938q-5767","url":"https://github.com/advisories/GHSA-m3x9-938q-5767","severity":"medium","published_at":"2026-11-12T21:05:41+00:00","modified_at":"2026-11-12T21:05:41+00:00","first_seen_at":"2026-11-12T21:09:02+00:00","fetched_at":"2026-11-12T21:29:46+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-126207","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-126207","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":138520,"url":"https://api.github.com/repos/flavorjones/loofah/security-advisories/GHSA-m3x9-938q-5767","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":138521,"url":"https://github.com/flavorjones/loofah/security/advisories/GHSA-m3x9-938q-5767","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":"2026-11-02T05:03:07+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":138523,"url":"https://github.com/flavorjones/loofah/commit/b198a36e44c9c5408163ac50787825e8ca0b166b","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"}