From 110dcc2e54618cc0fcfd7cc847a1688f21ebf948 Mon Sep 17 00:00:00 2001 From: scamiv <6170744+scamiv@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:47:55 +0100 Subject: [PATCH] Reject dot segments in asset URLs --- src/core/AssetUrls.ts | 15 ++++++++++++++- tests/AssetUrls.test.ts | 9 +++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/core/AssetUrls.ts b/src/core/AssetUrls.ts index 951cf325e..9a641391e 100644 --- a/src/core/AssetUrls.ts +++ b/src/core/AssetUrls.ts @@ -8,6 +8,19 @@ function safeDecodeAssetSegment(segment: string): string { } } +function assertSafeAssetSegment(segment: string): string { + const decodedSegment = safeDecodeAssetSegment(segment); + if ( + segment === "." || + segment === ".." || + decodedSegment === "." || + decodedSegment === ".." + ) { + throw new Error(`Invalid asset path segment: ${segment}`); + } + return decodedSegment; +} + export function encodeAssetPath(path: string): string { return normalizeAssetPath(path) .split("/") @@ -21,7 +34,7 @@ export function normalizeAssetPath(path: string): string { .replace(/^\/+/, "") .split("/") .filter((segment) => segment.length > 0) - .map((segment) => safeDecodeAssetSegment(segment)) + .map((segment) => assertSafeAssetSegment(segment)) .join("/"); } diff --git a/tests/AssetUrls.test.ts b/tests/AssetUrls.test.ts index 70f5bc581..934e629e2 100644 --- a/tests/AssetUrls.test.ts +++ b/tests/AssetUrls.test.ts @@ -27,4 +27,13 @@ describe("AssetUrls", () => { test("falls back to the unversioned path when manifest has no match", () => { expect(buildAssetUrl("images/unknown.svg", {})).toBe("/images/unknown.svg"); }); + + test("rejects dot segments in asset paths", () => { + expect(() => buildAssetUrl("../api/instance", {})).toThrow( + "Invalid asset path segment: ..", + ); + expect(() => buildAssetUrl("images/%2e%2e/secret.svg", {})).toThrow( + "Invalid asset path segment: %2e%2e", + ); + }); });