url: give blob: URLs a null origin when the inner host has invalid punycode - #39404
url: give blob: URLs a null origin when the inner host has invalid punycode#39404robobun wants to merge 1 commit into
Conversation
…nycode
URLDecomposition::origin() re-parses a blob: URL's path with WTF::URL
alone. Since the WebKit bump that removed the parser's own xn-- check,
that parse accepts hosts such as xn--a.com that the URL constructor,
URL.parse, URL.canParse and the host setters reject through
hasValidPunycodeHost, so new URL("blob:http://xn--a.com/").origin
returned "http://xn--a.com" while new URL("http://xn--a.com/") threw.
Apply the same host check to the inner URL, so it counts as unparsed and
the origin is "null", matching Node and Bun 1.3.
|
Status: ready for review. Reproduced on the released 1.4 build with |
|
Updated 9:23 PM PT - Aug 16th, 2026
✅ @robobun, your commit 8a8da159f557d258569adfc3952153e12b92f7be passed in 🧪 To try this PR locally: bunx bun-pr 39404That installs a local version of the PR into your bun-39404 --bun |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan includes up to 5 reviews per rolling hour; 0 remain after this review. WalkthroughBlob URL origin extraction now rejects inner URLs with unacceptable special-scheme hosts, including invalid ChangesBlob URL validation
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
LGTM — one-line fix reusing the existing hasAcceptableHost helper for the last path #34660 missed.
What was reviewed:
- Confirmed
origin()is the only remaining spot in URLDecomposition.cpp that constructs aWTF::URLfrom user input;setHost/setHostnamealready have this exact check. - Checked that the inner scheme filter (http/https/ftp/ws/wss/file) means only special schemes reach
protocolHostAndPort(), so the!hasSpecialScheme()short-circuit in the helper is inert here — the change is purely a tightening. - Test covers every scheme in that filter, plus positive cases (valid
xn--ls8h, Unicode → punycode, port,xn--ain path/query) proving no regression.
Extended reasoning...
Overview
Adds && hasAcceptableHost(subUrl) to the blob: branch of URLDecomposition::origin() so that a blob: URL whose inner path re-parses to a host with an invalid xn-- label yields origin "null" instead of a tuple origin. Also updates the helper's comment and adds a test in test/js/web/url/url.test.ts.
Security risks
Origin is security-relevant, but this change is strictly a tightening: inputs that previously produced a tuple origin now produce "null", matching Node, Bun 1.3.14, and the spec (an inner URL that fails to parse yields an opaque origin). No new surface is exposed and no origin becomes more permissive.
Level of scrutiny
Low-to-medium. The functional diff is a single boolean conjunct calling a static helper already used identically two places in the same file (setHost, setHostname). The PR description traces exactly why this path was missed (#34660 covered constructor/parse/canParse/href/host/hostname setters; blob-origin re-parse was the remaining site). The helper's || !url.hasSpecialScheme() fallback is moot here because the very next line only accepts special-scheme inner URLs anyway.
Other factors
- Test asserts both directions: nine invalid-punycode inner URLs (across http/https/ws/wss/ftp/file, including uppercase
XN--, percent-encodedx%6E--, multi-label, and degeneratexn---/xn--a-) each throw fromnew URL(inner)and give"null"fromnew URL("blob:"+inner).origin; five valid inner URLs (valid punycode, Unicode host, port,xn--ain path/query) still produce the correct tuple origin. - PR states the test fails on the released 1.4 build and passes with this change;
test/js/web/url(including WPT origin expectations) andtest/js/node/urlpass. - No memory, threading, or exception-scope concerns — pure const string logic on a stack-local
WTF::URL.
Problem
new URL("blob:http://xn--a.com/").originreturns"http://xn--a.com"on main, whilenew URL("http://xn--a.com/")throwsInvalid URLon the same build. Node and Bun 1.3.14 return"null"for the blob origin.URLDecomposition::origin()(src/jsc/bindings/URLDecomposition.cpp:49) derives a blob: URL's origin by re-parsing its path with a bareWTF::URLand only checksisValid().xn--labels, so that inner parse accepts invalid punycode. node compat batch: callback-throw dispatch, Assert class + native deep-equality parity, Intl gate + URL/buffer fallout, compile cache, watch kill-signal, profilers (+98 tests) #34660 restored the rejection (hasValidPunycodeHost) for the constructor,URL.parse,URL.canParse,href, and thehost/hostnamesetters, but not for this path, which is where the two answers diverged. In 1.3.14 the parser itself rejected the host, so both agreed.Fix
origin()now also requireshasAcceptableHost(subUrl), the helper thehost/hostnamesetters in the same file already use. An inner URL with an invalid punycode label is treated as unparsed, so the origin is"null".new URL(inner)would accept. Hosts withoutxn--return from the check without touching ICU; parser-produced punycode (from a Unicode inner host) still validates, which the test covers.test/js/web/url/url.test.ts, "blob: origin is null when the inner URL has an invalid punycode label (like Node)". Fails on the released 1.4 build (Received: "http://xn--a.com"), passes with this change.bun bd test test/js/web/url(1303 pass, includes the WPT url-constructor origin expectations) andtest/js/node/urlpass; the one failure there isurl-canParse-whatwg.test.jstiming out a 1e5-iteration loop under the debug ASAN build on a loaded box, which does not go throughorigin().Background
xn--label: the ASCII encoding of a Unicode domain label (xn--ls8his the pile of poo emoji). UTS Bun v0.0.41 #46 (IDNA) defines whichxn--labels are valid;xn--adoes not decode to anything, so ada (Node's URL parser) fails to parse hosts containing it.hasValidPunycodeHost(src/jsc/bindings/NodeURL.cpp): Bun's port of that check, run on top of WebKit's parser, which only lowercases all-ASCII hosts.hasAcceptableHostin URLDecomposition.cpp wraps it and skips non-special schemes, whose hosts are opaque and never go through IDNA."null".Before / after for the inner URLs checked
Where the rejection went: the WTF
URLParserat the WebKit pin used by Bun 1.3.14 still hadsubdomainStartsWithXNDashDash, which sent ASCIIxn--hosts through ICU; the pin from #32414 onward does not. Bun's own check (#34660) coversDOMURL::create,parse,canParse,setHref,setHostandsetHostname;origin()was the remaining path that parses user input into a host.[stamp-90s] gate passed · iteration 0 · 2 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 1 passed · 0 rejected · iteration 0
evidence per changed file