fix: allow arbitrary workspace file delivery

This commit is contained in:
2026-07-11 02:41:14 +08:00
parent 7fcb57013e
commit 5a65188b5d
5 changed files with 137 additions and 31 deletions
+20 -24
View File
@@ -1,4 +1,4 @@
import { extname, isAbsolute, join } from "node:path";
import { isAbsolute, join, relative, resolve, sep } from "node:path";
import {
WorkspaceFileBoundaryError,
readWorkspaceFileNoFollow,
@@ -10,39 +10,21 @@ export interface DeliverableFile {
readonly data: Buffer;
}
const DELIVERABLE_EXTENSIONS = new Set([
".csv",
".doc",
".docx",
".gif",
".jpeg",
".jpg",
".md",
".pdf",
".png",
".ppt",
".pptx",
".svg",
".txt",
".typ",
".xls",
".xlsx",
".zip",
]);
const DELIVERABLE_CPH_DIRECTORY = "inbox";
export async function resolveDeliverableFile(
requestedPath: string,
workspaceRoot: string,
workspaceDir: string,
maxBytes?: number,
): Promise<DeliverableFile | null> {
const token = requestedPath.trim();
if (token === "" || !DELIVERABLE_EXTENSIONS.has(extname(token).toLowerCase())) {
return null;
}
if (token === "") return null;
for (const candidate of candidatePaths(token)) {
assertNotPlatformRuntimePath(candidate, workspaceDir);
try {
return await readWorkspaceFileNoFollow(workspaceRoot, workspaceDir, candidate);
return await readWorkspaceFileNoFollow(workspaceRoot, workspaceDir, candidate, maxBytes);
} catch (error) {
if (!(error instanceof WorkspaceFileBoundaryError) || error.reason !== "not_found") {
throw error;
@@ -54,6 +36,20 @@ export async function resolveDeliverableFile(
return null;
}
function assertNotPlatformRuntimePath(candidate: string, workspaceDir: string): void {
const workspace = resolve(workspaceDir);
const target = isAbsolute(candidate) ? resolve(candidate) : resolve(workspace, candidate);
const rel = relative(workspace, target);
const components = rel.split(sep);
if (components[0] === ".cph" && components[1] !== DELIVERABLE_CPH_DIRECTORY) {
throw new WorkspaceFileBoundaryError(
`platform runtime files cannot be delivered: ${candidate}`,
candidate,
"boundary",
);
}
}
function candidatePaths(token: string): string[] {
if (isAbsolute(token)) return [token];
const candidates = [token];