{"id":"CVE-2026-110306","kind":"vulnerability","aliases":["CVE-2026-110306","GHSA-w74x-v6f9-j3xg"],"related":[],"title":"fast-uri vulnerable to URI scheme spoofing via percent-encoded scheme decoding","description":"### Impact\n\n`fast-uri` percent-decodes the scheme component with the legacy global `unescape()` before validating it, so an input whose decoded scheme is a valid but dangerous token is emitted as a live scheme. RFC 3986 section 3.1 does not allow percent-encoding in a scheme, so no RFC or WHATWG parser treats such input as having that scheme, but `fast-uri` does.\n\nFor example, `normalize('%6Aavascript:alert(1)')` returns `javascript:alert(1)`, `resolve('https://a.example/', '%6Aavascript:alert(1)')` returns `javascript:alert(1)`, and `parse('%6Aavascript:alert(1)').scheme` returns `javascript`. The `%uXXXX` form (`%u006Aavascript:`) produces the same result, and `equal('%68ttp://a/', 'http://a/')` returns `true`.\n\nAn application that screens dangerous schemes on the raw input and then stores or renders `normalize(input)` or `resolve(base, input)` turns an inert string into an executable `javascript:`, `data:`, or `vbscript:` URL, a stored or reflected cross-site scripting vector in the consuming application. `equal()` also treats an encoded and a plain scheme as the same, which can bypass equality-based allowlists.\n\n### Patches\n\nUpgrade to `fast-uri` `2.4.8`, `3.1.9`, or `4.2.3`.\n\n### Workarounds\n\nReject input whose scheme part (before the first `:`, and before any `/`, `?`, or `#`) contains `%`. Alternatively, check `parse(x).scheme` against an allowlist after normalization and use that same parsed result rather than the raw string.","summary":"fast-uri, an npm URI parser, percent-decodes URI schemes with unescape() before checking them, so an encoded input like %6Aavascript:alert(1) comes out as a live javascript: URL. An application that filters dangerous schemes on the raw input and then renders the normalized or resolved URL can be made to run attacker script in its pages (XSS).","severity":"medium","cvss":6.1,"epss":0.00204,"exploited":false,"exploited_at":null,"products":[{"name":"fast-uri","fixed":["2.4.8"],"affected":"< 2.4.8","ecosystem":"npm"},{"name":"fast-uri","fixed":["3.1.9"],"affected":">= 3.0.0, < 3.1.9","ecosystem":"npm"},{"name":"fast-uri","fixed":["4.2.3"],"affected":">= 4.0.0, < 4.2.3","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["cwe-172","cwe-180","javascript","node","npm","package","web-apps"],"not_affected_if":["fast-uri-not-installed","no-user-supplied-urls-rendered","static-export"],"how_to_check":"Run `npm ls fast-uri` and check the resolved version against the affected ranges (<2.4.8, >=3.0.0 <3.1.9, >=4.0.0 <4.2.3).\nFind code that calls normalize(), resolve() or equal() from fast-uri on user-supplied URLs and see whether the result is rendered or stored as a link.","fix":"Upgrade fast-uri to 2.4.8, 3.1.9 or 4.2.3 matching your major line, then rebuild and redeploy.\nUntil then, reject input whose scheme part (before the first ':' and before any '/', '?' or '#') contains '%'.","status":"open","published_at":"2026-10-20T07:17:08+00:00","first_seen_at":"2026-10-23T21:45:07+00:00","triaged_at":"2026-10-23T21:45:28+00:00","updated_at":"2026-10-23T21:57:20+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-23T21:45:28Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade fast-uri to 2.4.8, 3.1.9 or 4.2.3 matching your major line, then rebuild and redeploy.\nUntil then, reject input whose scheme part (before the first ':' and before any '/', '?' or '#') contains '%'.","reach":"internet","impact":"medium","summary":"fast-uri, an npm URI parser, percent-decodes URI schemes with unescape() before checking them, so an encoded input like %6Aavascript:alert(1) comes out as a live javascript: URL. An application that filters dangerous schemes on the raw input and then renders the normalized or resolved URL can be made to run attacker script in its pages (XSS).","category":"package","who_cares":["npm","javascript","node","web-apps"],"confidence":0.8,"needs_auth":"unknown","worst_case":"Attacker-controlled javascript: URL rendered in victim's browser as XSS","analyst_note":"","how_to_check":"Run `npm ls fast-uri` and check the resolved version against the affected ranges (<2.4.8, >=3.0.0 <3.1.9, >=4.0.0 <4.2.3).\nFind code that calls normalize(), resolve() or equal() from fast-uri on user-supplied URLs and see whether the result is rendered or stored as a link.","needs_analyst":false,"not_affected_if":["fast-uri-not-installed","no-user-supplied-urls-rendered","static-export"],"user_interaction":"unknown","needs_special_setup":"no","severity_if_unknown":"medium"},"cost_usd":0.000608,"input_tokens":3400,"output_tokens":537,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-110306':17A,25A '-172':36B '-180':38B '-2026':16A,24A '/'',':187C '/a':214C '1':70B,179C,183C,190C,194C,199C '2.4.8':289C '3.1':146C '3.1.9':290C '3986':144C '4.2.3':292C '68ttp':213C '6aavascript':68B,177C,188C,197C 'a.example':186C 'a.example/'',':185C 'alert':69B,178C,182C,189C,193C,198C 'allow':149C 'allowlist':282C,314C 'also':265C 'altern':307C 'app':45B 'applic':79B,219C,263C 'attack':101B 'base':237C,281C 'bypass':278C 'check':61B,308C 'come':71B 'compon':116C 'consum':262C 'contain':306C 'cross':256C 'cross-sit':255C 'cve':15A,23A 'cwe':35B,37B 'danger':82B,135C,222C 'data':247C 'decod':14A,55B,113C,129C 'emit':138C 'encod':12A,65B,152C,268C 'equal':212C,264C,280C 'equality-bas':279C 'exampl':175C 'execut':245C 'fast':2A,27A,30A,33A,47B,109C,171C,287C 'fast-uri':1A,26A,29A,32A,46B,108C,170C,286C 'filter':81B 'first':301C 'form':205C 'ghsa':19A 'ghsa-w74x-v6f9-j3xg':18A 'global':120C 'impact':107C 'inert':241C 'input':66B,87B,127C,164C,227C,234C,238C,295C 'j3xg':22A 'javascript':39B,76B,181C,192C,202C,246C 'legaci':119C 'like':67B 'live':75B,141C 'made':98B 'node':40B 'normal':92B,176C,233C,316C 'npm':41B,50B 'packag':42B 'page':105B 'pars':196C,309C,321C 'parser':52B,161C 'part':298C 'patch':283C 'percent':11A,54B,112C,151C 'percent-decod':53B,111C 'percent-encod':10A,150C 'plain':271C 'produc':207C 'rather':323C 'raw':86B,226C,326C 'reflect':254C 'reject':294C 'render':90B,232C 'resolv':94B,184C,236C 'result':210C,322C 'return':180C,191C,201C,216C 'rfc':143C,158C 'run':100B 'scheme':7A,13A,57B,83B,115C,130C,142C,155C,168C,200C,223C,272C,297C,311C 'screen':221C 'script':102B,258C 'section':145C 'site':257C 'spoof':8A 'store':230C,252C 'string':242C,327C 'token':136C 'treat':162C,266C 'true':217C 'turn':239C 'u006aavascript':206C 'unescap':59B,121C 'upgrad':284C 'uri':3A,6A,28A,31A,34A,48B,51B,56B,110C,172C,288C 'url':77B,95B,250C 'use':318C 'uxxxx':204C 'v6f9':21A 'valid':123C,133C 'vbscript':249C 'vector':259C 'via':9A 'vulner':4A 'w74x':20A 'web':44B 'web-app':43B 'whatwg':160C 'whose':128C,296C 'workaround':293C 'x':310C 'xss':106B","nicknames":[],"mitigations":[],"who_affected":null,"tier":"C","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-20T07:17:08+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-w74x-v6f9-j3xg","url":"https://github.com/advisories/GHSA-w74x-v6f9-j3xg","severity":"medium","published_at":"2026-10-23T21:41:36+00:00","modified_at":"2026-10-23T21:41:38+00:00","first_seen_at":"2026-10-23T21:45:07+00:00","fetched_at":"2026-10-23T21:45:07+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-110306","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-110306","severity":null,"published_at":"2026-10-20T07:17:08+00:00","modified_at":"2026-10-20T20:17:14+00:00","first_seen_at":"2026-10-23T21:57:20+00:00","fetched_at":"2026-10-23T22:28:57+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}