Skip to content

Bump WebKit (oven-sh/WebKit#909 preview): IPv6 hosts follow the IPv6 parser of the URL Standard - #47236

Draft
robobun wants to merge 2 commits into
mainfrom
robobun/db9d283d/url-ipv6-trailing-colon
Draft

robobun wants to merge 2 commits into
mainfrom
robobun/db9d283d/url-ipv6-trailing-colon

Conversation

@robobun

@robobun robobun commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Draft until oven-sh/WebKit#909 merges. WEBKIT_VERSION is its preview build: the fork commit fcc1ea09, which main pinned when this was opened, plus two commits. The merged commit replaces it before this leaves draft.

Problem

  • new URL("http://[::1:]/") gives http://[::1]/, and fetch("http://[::1:]:3000/") connects to [::1]. The URL Standard says failure. Node.js, Chromium and rust-url refuse the URL.
  • new URL("http://[2001:db8:1234::5efe:127.0.0.1]/") throws. The URL Standard parses it.
  • Both are in URLParser::parseIPv6Host in WTF (vendor/WebKit/Source/WTF/wtf/URLParser.cpp:3293, :3332).

Fix

  • [WTF] URLParser: parseIPv6Host follows the IPv6 parser of the URL Standard WebKit#909 changes the two places. No Bun source changes.
  • Tests in the existing files: url.test.ts, urlpattern.test.ts and url-domain-ascii-unicode.test.js.
  • Verified: bun bd test on the three files gives 5 failures on main's pin and 574 passes with this pin. test/js/web/url, test/js/web/urlpattern and test/js/node/url: 765 pass, 0 fail.
  • The url.domainToUnicode loop over invalid inputs called url.domainToASCII. It now calls url.domainToUnicode.

Background

  • An IPv6 piece is up to four hex digits. :: (a compression) is a run of zero pieces.
  • Every URL entry point of Bun uses WTF::URL, so one parser covers them all.
  • No user reported this. A differential test against Node.js found it. zod's z.ipv6() uses URL.canParse, so it accepts ::1: on Bun.
  • Considered a check in URLDecomposition.cpp: it cannot cover the constructor or URLPattern.

Downsides

Notes

Repro.

for (const s of ["http://[::1:]/", "http://[1::2:]:81/", "http://[1:2:3:4:5:6:7:]/", "http://[1::]/", "http://[2001:db8:1234::5efe:127.0.0.1]/"]) {
  let r; try { r = new URL(s).href; } catch { r = "throws"; }
  console.log(JSON.stringify(s), "->", r);
}
input main this pin, and Node.js v26.3.0
http://[::1:]/ http://[::1]/ throws
http://[1::2:]:81/ http://[1::2]:81/ throws
http://[1:2:3:4:5:6:7:]/ throws throws
http://[1::]/ http://[1::]/ http://[1::]/
http://[2001:db8:1234::5efe:127.0.0.1]/ throws http://[2001:db8:1234::5efe:7f00:1]/

The contract. URL Standard, IPv6 parser. After a piece and a :: "If c is the EOF code point, IPv6-invalid-code-point validation error, return failure." For an IPv4 address at the end, the position check is: "If pieceIndex is greater than 6, IPv4-in-IPv6-too-many-pieces validation error, return failure." WTF parses the first case and drops the colon (upstream chose that in 2019, bug 199627, on a wrong reading of the standard). It rejects the second case when a compression leaves the IPv4 address at piece 5. oven-sh/WebKit#909 has the history and the measurements.

The pin. autobuild-preview-pr-909-c061b103 is fcc1ea0973a4 plus two commits: the commit of oven-sh/WebKit#648 (the IPv4 tail) and the trailing colon fix. If a WebKit upgrade brings the merged commits to main first, this pull request drops the pin change and keeps the tests.

Tests.

  • test/js/web/url/url.test.ts: two tests. The rejected hosts go through new URL, URL.parse, URL.canParse, a base URL, the href, host and hostname setters, Request, WebSocket and fetch. Controls: a final :: stays valid, and the setters still take a valid bracketed value.
  • test/js/web/urlpattern/urlpattern.test.ts: a URL input, a hostname input and a base URL with the trailing colon do not match.
  • test/js/node/url/url-domain-ascii-unicode.test.js: [::1:] gives "" from url.domainToASCII and url.domainToUnicode. The five older rows of that table were never checked against url.domainToUnicode, because its loop called url.domainToASCII. They pass.

Every expected value is also what Node.js v26.3.0 gives. The five failures on main's pin are the two tests in url.test.ts, the URLPattern test, and the [::1:] row in each of the two describe blocks.

How much differs from Node.js. 334,619 bracketed hosts (every string of length 0 to 8 over 0 1 : ., 300,000 random token strings, and a structured set), each parsed by Bun and by Node.js v26.3.0. Bun 1.4.3 differs on 488. The debug build of this branch differs on 0.

Other suites. The 42 test-url* and test-whatwg-url* files of the Node.js suite give the same exit codes as before.


[policy-decision:webkit] gate passed · iteration 0 · 4 files touched

passes on PR (with fix)
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/js/node/url/url-domain-ascii-unicode.test.js' 'test/js/web/url/url.test.ts' 'test/js/web/urlpattern/urlpattern.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/js/node/url/url-domain-ascii-unicode.test.js test/js/web/url/url.test.ts test/js/web/urlpattern/urlpattern.test.ts
bun test v1.4.4 (367d939d9)

test/js/web/url/url.test.ts:
(pass) url > URL throws [31.85ms]
(pass) url > ERR_INVALID_URL carries input and, when given, base [6.05ms]
(pass) url > should have correct origin and protocol [7.28ms]
(pass) url > blob urls [5.48ms]
(pass) url > leaves opaque (non-special-scheme) hosts unchanged [7.48ms]
(pass) url > special-scheme hosts use the Unicode 16 IDNA table [4.32ms]
(pass) url > rejects an IPv6 host that ends with a single colon (URL Standard IPv6 parser) [43.32ms]
(pass) url > parses a compressed IPv6 host that ends with an IPv4 address (URL Standard IPv6 parser) [5.63ms]
(pass) url > rejects invalid punycode labels however they are spelled in the input (like Node) [15.86ms]
(pass) url > judges literal punycode labels like Node (fast path and ICU path) [17.31ms]
(pass) url > resolves against repeated, alternating and invalid string bases consistently [14.61ms]
(pass) url > href, toString and toJSON agree before and after mutation [272.41ms]
(pass) url > prints [94.60ms]
(pass) url > URLContext offsets account for the /. pathname guard [75.32ms]
(pass) url > works [9.65ms]
(pass) url > URL.canParse > URL.canParse(undefined, undefined) [2.35ms]
(pass) url > URL.canParse > URL.canParse(a:b, undefined) [0.31ms]
(pass) url > URL.canParse > URL.canParse(undefined, a:b) [0.15ms]
(pass) url > URL.canParse > URL.canParse(a:/b, undefined) [0.10ms]
(pass) url > URL.canParse > URL.canParse(undefined, a:/b) [0.96ms]
(pass) url > URL.canParse > URL.canParse(https://test:test, undefined) [0.13ms]
(pass) url > URL.canParse > URL.canParse(a, https://b/) [0.26ms]
(pass) url > URL.canParse > URL.canParse.length should be 1 [1.65ms]
(pass) url > URLSearchParams constructed from an object interleaves Get with value conversion [5.45ms]
(pass) 
... (truncated)
Exit: 0
diff hotspot
scripts/build/deps/webkit.ts                      |  2 +-
 test/js/node/url/url-domain-ascii-unicode.test.js |  4 +-
 test/js/web/url/url.test.ts                       | 84 +++++++++++++++++++++++
 test/js/web/urlpattern/urlpattern.test.ts         | 23 +++++++
 4 files changed, 111 insertions(+), 2 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                               reads  edits  tests
scripts/build/deps/webkit.ts                           1      1     23
test/js/node/url/url-domain-ascii-unicode.test.js      2      2     13
test/js/web/url/url.test.ts                            2      2     18
test/js/web/urlpattern/urlpattern.test.ts              1      1     13

The preview is main's pin fcc1ea0973a4 plus two commits in
URLParser::parseIPv6Host: a compressed IPv6 address takes an IPv4 tail at
piece 5, and an address that ends with a single ':' does not parse.

The merged commit replaces the preview tag before this leaves draft.
An IPv6 host that ends with a single colon does not parse. A compressed IPv6
host with an IPv4 address at the end parses.

new URL, URL.parse, URL.canParse, the href, host and hostname setters,
Request, WebSocket, fetch, URLPattern inputs, url.domainToASCII and
url.domainToUnicode all reach the same host parser. Every expected value is
also what Node.js v26.3.0 gives.

The url.domainToUnicode loop over the invalid inputs called
url.domainToASCII. It calls url.domainToUnicode now.
@robobun

robobun commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: waiting for oven-sh/WebKit#909, which has the fix. This pull request pins its preview build and adds the tests.

Reproduced on Bun 1.4.3 and on main (6c3d1bf956, debug ASAN build):

console.log(new URL("http://[::1:]/").href); // http://[::1]/   Node.js v26.3.0 throws
try { new URL("http://[2001:db8:1234::5efe:127.0.0.1]/"); } catch (e) { console.log(e.code); } // ERR_INVALID_URL   Node.js parses it

bun bd test test/js/web/url/url.test.ts test/js/web/urlpattern/urlpattern.test.ts test/js/node/url/url-domain-ascii-unicode.test.js: 5 failures on main's pin, 574 passes with this pin.

CI on c035c8a0 (build 134623): the tests of this pull request pass on every lane, Linux, Windows and macOS. One required job is red: test/aot/perf/start.test.ts on debian 13 x64. This diff does not touch that test. #47202 explains the failure: one 2 MB huge page in the executable's zero-filled data, which the link address decides. #47129 has fixed it on main since. The test runs here because a change of WEBKIT_VERSION makes CI run the whole test/aot suite.

Main's pin has moved several times since that build, so this branch conflicts on the WEBKIT_VERSION line. When oven-sh/WebKit#909 merges, this pull request is rebased and takes the merged commit as its pin.

@robobun

robobun commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 7:10 PM PT - Oct 28th, 2026

❌ @robobun, your commit c035c8a has 58 failures in Build #134623 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 47236

That installs a local version of the PR into your bun-47236 executable, so you can run:

bun-47236 --bun

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant