From 2985d11b02f8eccb3b25840f4226a6a00c5d4b06 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 11 Jul 2026 02:35:53 +0000 Subject: [PATCH 1/2] fetch: reject non-HTTP(S) schemes before touching the network fetch() with a URL whose scheme is not http/https/s3 must reject with TypeError before any network step. The existing scheme gate in fetch_impl was wrapped in `if !url.protocol.is_empty()`, but the simple URL parser only populates `protocol` when it sees `://`. WHATWG-valid URLs without an authority (`about:blank`, `javascript:...`, `localhost:3000/x`) therefore reached the HTTP connect path with the scheme name treated as a hostname, issuing real DNS lookups for `about`, `javascript`, etc. Drop the is_empty guard. By this point the input has already been WHATWG-normalized, and http/https are special schemes that always normalize to `scheme://host`, so `is_http()`/`is_https()` are reliable. data:, file:, and blob: URLs are handled earlier and are unaffected. --- src/runtime/webcore/fetch.rs | 24 +++++++-------- test/js/web/fetch/fetch-args.test.ts | 45 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/runtime/webcore/fetch.rs b/src/runtime/webcore/fetch.rs index a98917610fd5..77632c9e3d29 100644 --- a/src/runtime/webcore/fetch.rs +++ b/src/runtime/webcore/fetch.rs @@ -1593,19 +1593,17 @@ fn fetch_impl( )); } - if !url.protocol.is_empty() { - if !(url.is_http() || url.is_https() || url.is_s3()) { - let err = global_this.to_type_error( - jsc::ErrorCode::INVALID_ARG_VALUE, - format_args!("protocol must be http:, https: or s3:"), - ); - return Ok( - JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm( - global_this, - err, - ), - ); - } + if !(url.is_http() || url.is_https() || url.is_s3()) { + let err = global_this.to_type_error( + jsc::ErrorCode::INVALID_ARG_VALUE, + format_args!("protocol must be http:, https: or s3:"), + ); + return Ok( + JSPromise::dangerously_create_rejected_promise_value_without_notifying_vm( + global_this, + err, + ), + ); } if !ALLOW_GET_BODY && !method.has_request_body() && body.has_body() && !upgraded_connection { diff --git a/test/js/web/fetch/fetch-args.test.ts b/test/js/web/fetch/fetch-args.test.ts index 3d245e817493..ae2ce83b7932 100644 --- a/test/js/web/fetch/fetch-args.test.ts +++ b/test/js/web/fetch/fetch-args.test.ts @@ -16,6 +16,51 @@ afterAll(() => { server!.stop(true); }); +describe("non-HTTP(S) URL scheme rejection", () => { + // WHATWG URL parses `localhost:3000/x` as scheme "localhost:" with an empty + // host. fetch() must reject these without any network activity rather than + // treating the scheme name as a hostname. + test.each(["about:blank", "javascript:alert(1)", "chrome:flags", "foo:bar"])( + "fetch(%j) rejects with TypeError", + async input => { + const prevCount = requestCount; + const err = await fetch(input).then( + () => null, + e => e, + ); + expect(err).toBeInstanceOf(TypeError); + expect(requestCount).toBe(prevCount); + }, + ); + + test("fetch('localhost:/path') does not reach the network", async () => { + const prevCount = requestCount; + const input = `localhost:${server!.port}/api/x?q=1`; + expect(new URL(input).host).toBe(""); + const err = await fetch(input).then( + () => null, + e => e, + ); + expect(err).toBeInstanceOf(TypeError); + expect(requestCount).toBe(prevCount); + }); + + test("fetch(new Request('about:blank')) rejects with TypeError", async () => { + const err = await fetch(new Request("about:blank")).then( + () => null, + e => e, + ); + expect(err).toBeInstanceOf(TypeError); + }); + + test("http:// and data: still work", async () => { + const res = await fetch(server!.url); + expect(res.status).toBe(200); + const dataRes = await fetch("data:text/plain,hello"); + expect(await dataRes.text()).toBe("hello"); + }); +}); + test("fetch(request subclass with headers)", async () => { class MyRequest extends Request { constructor(input: RequestInfo, init?: RequestInit) { From a3619b52dd8be4fb2a0d23a662bafde967917e9c Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 11 Jul 2026 02:56:51 +0000 Subject: [PATCH 2/2] ci: retrigger