From 521874697238f8022dc5cee06fafc3ac9d7a7bf2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 6 May 2026 21:54:15 +0100 Subject: [PATCH] fix(temp): preserve workspace leaf filename contract --- src/private-temp-workspace.ts | 11 +++++++++-- test/findings-regression.test.ts | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/private-temp-workspace.ts b/src/private-temp-workspace.ts index 4142d72..cbe40be 100644 --- a/src/private-temp-workspace.ts +++ b/src/private-temp-workspace.ts @@ -9,7 +9,6 @@ import { type FileStoreSync, } from "./file-store.js"; import { openRootFileSync } from "./root-file.js"; -import { isSafePathSegment } from "./safe-path-segment.js"; import { registerTempPathForExit } from "./temp-cleanup.js"; export type TempWorkspaceOptions = { @@ -62,7 +61,15 @@ function resolveWorkspaceLeaf(dir: string, fileName: string): string { function assertWorkspaceFileName(fileName: string): string { const value = fileName.trim(); - if (!isSafePathSegment(value)) { + if ( + !value || + value === "." || + value === ".." || + value.includes("\0") || + value.includes("/") || + value.includes("\\") || + path.basename(value) !== value + ) { throw new Error(`Invalid temp workspace file name: ${JSON.stringify(fileName)}`); } return value; diff --git a/test/findings-regression.test.ts b/test/findings-regression.test.ts index 044beaf..de7ba81 100644 --- a/test/findings-regression.test.ts +++ b/test/findings-regression.test.ts @@ -15,7 +15,7 @@ import { runPinnedWriteHelper } from "../src/pinned-write.js"; import { replaceFileAtomic } from "../src/replace-file.js"; import { writeViaSiblingTempPath } from "../src/sibling-temp.js"; import { sanitizeTempFileName, tempFile } from "../src/temp-target.js"; -import { tempWorkspaceSync } from "../src/private-temp-workspace.js"; +import { tempWorkspace, tempWorkspaceSync } from "../src/private-temp-workspace.js"; import { __setFsSafeTestHooksForTest } from "../src/test-hooks.js"; import { movePathToTrash } from "../src/trash.js"; @@ -390,6 +390,19 @@ describe("security finding regressions", () => { } }); + it("accepts safe temp workspace leaf names with spaces and dot prefixes", async () => { + await using workspace = await tempWorkspace({ + rootDir: await tempRoot("fs-safe-workspace-leaf-"), + prefix: "work-", + }); + + await workspace.writeText("report 2026.txt", "ok"); + await workspace.writeText(".env", "TOKEN=ok"); + + await expect(workspace.read("report 2026.txt")).resolves.toEqual(Buffer.from("ok")); + await expect(workspace.read(".env")).resolves.toEqual(Buffer.from("TOKEN=ok")); + }); + it.runIf(process.platform !== "win32")("pins sync temp workspace reads against final symlink swaps", async () => { const base = await tempRoot("fs-safe-temp-workspace-sync-read-"); const outside = await tempRoot("fs-safe-temp-workspace-sync-outside-");