From 627553361328d2d2b39c31e66dc9464e16a150d8 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Mon, 19 Oct 2026 01:32:27 +0200 Subject: [PATCH] fix: keep the Lite dev server inside its export directory and survive a malformed path --- scripts/lite/serve.mjs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/lite/serve.mjs b/scripts/lite/serve.mjs index 103900c2e..7887db5dd 100644 --- a/scripts/lite/serve.mjs +++ b/scripts/lite/serve.mjs @@ -23,7 +23,7 @@ import { createServer, request as httpRequest } from "node:http"; import { request as httpsRequest } from "node:https"; import { createReadStream, existsSync, readFileSync, statSync } from "node:fs"; -import { extname, join, normalize, resolve } from "node:path"; +import { extname, join, normalize, resolve, sep } from "node:path"; import { LITE_SURFACES, collectZipEntries, emulateStalwartRequest, isReservedStalwartSegment, listZipEntries, readZipEntry } from "./lib.mjs"; const flags = process.argv.slice(2).filter((a) => a.startsWith("--")); @@ -61,9 +61,17 @@ function send(res, file, status = 200) { } function fileFor(pathname) { - const rel = normalize(decodeURIComponent(pathname)).replace(/^[/\\]+/, ""); + let decoded; + try { + decoded = decodeURIComponent(pathname); + } catch { + return null; // a malformed escape is a 404, not an uncaught URIError + } + const rel = normalize(decoded).replace(/^[/\\]+/, ""); const direct = join(root, rel); - if (!direct.startsWith(root)) return null; + // Inside the export directory itself, not a sibling that shares its name + // as a prefix (`out` vs `out-old`). + if (direct !== root && !direct.startsWith(root + sep)) return null; if (existsSync(direct) && statSync(direct).isFile()) return direct; const index = join(direct, "index.html"); if (existsSync(index)) return index;