fix: distinguish outside-workspace errors from not-found in fs-safe

When editing a file outside the workspace root, SafeOpenError previously
used the "invalid-path" code with the message "path escapes root". This
was indistinguishable from other invalid-path errors (hardlinks, symlinks,
non-files) and consumers often fell back to a generic "not found" message,
which was misleading.

Add a new "outside-workspace" error code with the message "file is outside
workspace root" so consumers can surface a clear, accurate error message.

- fs-safe.ts: add "outside-workspace" to SafeOpenErrorCode, use it for
  all path-escapes-root checks in openFileWithinRoot/writeFileWithinRoot
- pi-tools.read.ts: map "outside-workspace" to EACCES instead of rethrowing
- browser/paths.ts: return specific "File is outside {scopeLabel}" message
- media/server.ts: return 400 with descriptive message for outside-workspace
- fs-safe.test.ts: update traversal test expectations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
YuzuruS
2026-02-28 20:37:36 +09:00
committed by Ayaan Zaidi
parent 150c2093fa
commit f5c2be1910
5 changed files with 21 additions and 7 deletions

View File

@@ -67,7 +67,7 @@ describe("fs-safe", () => {
rootDir: root,
relativePath: path.join("..", path.basename(outside), "outside.txt"),
}),
).rejects.toMatchObject({ code: "invalid-path" });
).rejects.toMatchObject({ code: "outside-workspace" });
});
it.runIf(process.platform !== "win32")("blocks symlink escapes under root", async () => {
@@ -131,7 +131,7 @@ describe("fs-safe", () => {
relativePath: "../escape.txt",
data: "x",
}),
).rejects.toMatchObject({ code: "invalid-path" });
).rejects.toMatchObject({ code: "outside-workspace" });
});
it.runIf(process.platform !== "win32")("rejects writing through hardlink aliases", async () => {