e2e: a malformed percent-escape is a 404, not a crash
commit 0c5f1e6583eb6f43524d4095b6c17ff7cc768cc5 · parent 7defb72359
· browse this tree
Ximianth, 2026-10-10 05:50:33 UTC
decodeURIComponent throws on /%zz; it was outside the try, so the request handler threw and took the whole run down (Chrome and profile dir orphaned). Reproduced with a fetch against a started harness; 404 now. The comment credited startsWith with the .. protection; path.normalize is what drops leading .. from an absolute path.
diff --git a/packages/e2e/harness.ts b/packages/e2e/harness.ts index abf1dfc..a485a96 100644 --- a/packages/e2e/harness.ts +++ b/packages/e2e/harness.ts @@ -43,17 +43,19 @@ async function pollUntil( throw new Error(`timed out waiting for ${what}`); } -// Serves STATIC_DIR read-only on an ephemeral port. Paths are normalized and -// pinned under STATIC_DIR; anything else is a 404 (a test server, not a product). +// Serves STATIC_DIR read-only on an ephemeral port. Anything that isn't a +// readable file under it is a 404 (a test server, not a product), including a +// malformed percent-escape. `..` can't escape: normalize() drops leading `..` +// from an absolute path, so the startsWith check below is a pin, not the guard. function serveStatic(): Promise<{ baseUrl: string; server: Server }> { const srv = createServer((req, res) => { - const rel = normalize(decodeURIComponent(new URL(req.url ?? "/", "http://x").pathname)); - const file = join(STATIC_DIR, rel === "/" ? "gallery.html" : rel); - if (!file.startsWith(STATIC_DIR)) { - res.writeHead(404).end(); - return; - } try { + const rel = normalize(decodeURIComponent(new URL(req.url ?? "/", "http://x").pathname)); + const file = join(STATIC_DIR, rel === "/" ? "gallery.html" : rel); + if (!file.startsWith(STATIC_DIR)) { + res.writeHead(404).end(); + return; + } const body = readFileSync(file); res.writeHead(200, { "content-type": MIME[extname(file)] ?? "application/octet-stream" }); res.end(body);