From fb57c8643fb8b4733cbac9090ef58fd13a3b5347 Mon Sep 17 00:00:00 2001 From: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:18:39 +0000 Subject: [PATCH] fix(javascript): reject encoded separators joined by HTML URL whitespace #1287 removes URL tabs and newlines from an HTML script URL before percent-decoding. A declaration such as %2fapp.js passed the raw admission check and then decoded to a separator, resolving to a file the encoded-separator rule must refuse. Apply canonical path admission again to the parsed URL text. Co-Authored-By: Claude Opus 5.5 --- .../javascript/JavaScriptArtifactPathResolution.ts | 6 ++++++ .../javascript/JavaScriptHtmlBaseResolution.test.ts | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/application/javascript/JavaScriptArtifactPathResolution.ts b/src/application/javascript/JavaScriptArtifactPathResolution.ts index c2485a11d..bb93419d7 100644 --- a/src/application/javascript/JavaScriptArtifactPathResolution.ts +++ b/src/application/javascript/JavaScriptArtifactPathResolution.ts @@ -264,6 +264,12 @@ const htmlCandidate = ( return unresolvedOutcome(input, "external", [ "The document base href is external, so its script reference is not a local artifact path.", ]); + // Removing URL tabs and newlines can join an encoded dot or separator that + // the raw declaration split, so admit the parsed URL text again. + if (!admitsCanonicalPathSyntax(declared)) + return unresolvedOutcome(input, "rejected", [ + "Encoded dot or separator bytes are rejected before artifact path resolution.", + ]); const declaredPath = percentDecodeUrlPath(declared); if (declaredPath === null) return unresolvedOutcome(input, "rejected", [ diff --git a/src/application/javascript/JavaScriptHtmlBaseResolution.test.ts b/src/application/javascript/JavaScriptHtmlBaseResolution.test.ts index 49a5f7ad2..681d79099 100644 --- a/src/application/javascript/JavaScriptHtmlBaseResolution.test.ts +++ b/src/application/javascript/JavaScriptHtmlBaseResolution.test.ts @@ -150,6 +150,12 @@ describe("HTML reference URL text", () => { }); }, ); + it("rejects a base href whose URL whitespace splits an encoded separator", () => { + expect(resolve("app.js", "a%2\nf/")).toMatchObject({ + resolution_status: "rejected", + resolved_path: null, + }); + }); it("rejects a base href that decodes to NUL", () => { expect(resolve("app.js", "/a%00/")).toMatchObject({ resolution_status: "rejected", @@ -164,7 +170,7 @@ describe("HTML reference URL text", () => { }); }, ); - it.each(["%2e%2e/app.js", "a%2fapp.js"])( + it.each(["%2e%2e/app.js", "a%2fapp.js", "%2\nfapp.js", "%2\ne%2\te/app.js"])( "still rejects encoded dot and separator bytes in %j", (declared) => { expect(resolve(declared, "")).toMatchObject({