refactor(browser): split act route modules and dedupe path guards

This commit is contained in:
Peter Steinberger
2026-02-26 01:21:20 +01:00
parent 046feb6b0e
commit f41715a18f
7 changed files with 319 additions and 275 deletions

View File

@@ -9,6 +9,58 @@ export const DEFAULT_TRACE_DIR = DEFAULT_BROWSER_TMP_DIR;
export const DEFAULT_DOWNLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "downloads");
export const DEFAULT_UPLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "uploads");
type InvalidPathResult = { ok: false; error: string };
function invalidPath(scopeLabel: string): InvalidPathResult {
return {
ok: false,
error: `Invalid path: must stay within ${scopeLabel}`,
};
}
async function resolveRealPathIfExists(targetPath: string): Promise<string | undefined> {
try {
return await fs.realpath(targetPath);
} catch {
return undefined;
}
}
async function resolveTrustedRootRealPath(rootDir: string): Promise<string | undefined> {
try {
const rootLstat = await fs.lstat(rootDir);
if (!rootLstat.isDirectory() || rootLstat.isSymbolicLink()) {
return undefined;
}
return await fs.realpath(rootDir);
} catch {
return undefined;
}
}
async function validateCanonicalPathWithinRoot(params: {
rootRealPath: string;
candidatePath: string;
expect: "directory" | "file";
}): Promise<"ok" | "not-found" | "invalid"> {
try {
const candidateLstat = await fs.lstat(params.candidatePath);
if (candidateLstat.isSymbolicLink()) {
return "invalid";
}
if (params.expect === "directory" && !candidateLstat.isDirectory()) {
return "invalid";
}
if (params.expect === "file" && !candidateLstat.isFile()) {
return "invalid";
}
const candidateRealPath = await fs.realpath(params.candidatePath);
return isPathInside(params.rootRealPath, candidateRealPath) ? "ok" : "invalid";
} catch (err) {
return isNotFoundPathError(err) ? "not-found" : "invalid";
}
}
export function resolvePathWithinRoot(params: {
rootDir: string;
requestedPath: string;
@@ -42,51 +94,30 @@ export async function resolveWritablePathWithinRoot(params: {
return lexical;
}
const invalid = (): { ok: false; error: string } => ({
ok: false,
error: `Invalid path: must stay within ${params.scopeLabel}`,
});
const rootDir = path.resolve(params.rootDir);
let rootRealPath: string;
try {
const rootLstat = await fs.lstat(rootDir);
if (!rootLstat.isDirectory() || rootLstat.isSymbolicLink()) {
return invalid();
}
rootRealPath = await fs.realpath(rootDir);
} catch {
return invalid();
const rootRealPath = await resolveTrustedRootRealPath(rootDir);
if (!rootRealPath) {
return invalidPath(params.scopeLabel);
}
const requestedPath = lexical.path;
const parentDir = path.dirname(requestedPath);
try {
const parentLstat = await fs.lstat(parentDir);
if (!parentLstat.isDirectory() || parentLstat.isSymbolicLink()) {
return invalid();
}
const parentRealPath = await fs.realpath(parentDir);
if (!isPathInside(rootRealPath, parentRealPath)) {
return invalid();
}
} catch {
return invalid();
const parentStatus = await validateCanonicalPathWithinRoot({
rootRealPath,
candidatePath: parentDir,
expect: "directory",
});
if (parentStatus !== "ok") {
return invalidPath(params.scopeLabel);
}
try {
const targetLstat = await fs.lstat(requestedPath);
if (targetLstat.isSymbolicLink() || !targetLstat.isFile()) {
return invalid();
}
const targetRealPath = await fs.realpath(requestedPath);
if (!isPathInside(rootRealPath, targetRealPath)) {
return invalid();
}
} catch (err) {
if (!isNotFoundPathError(err)) {
return invalid();
}
const targetStatus = await validateCanonicalPathWithinRoot({
rootRealPath,
candidatePath: requestedPath,
expect: "file",
});
if (targetStatus === "invalid") {
return invalidPath(params.scopeLabel);
}
return lexical;
@@ -141,13 +172,8 @@ async function resolveCheckedPathsWithinRoot(params: {
allowMissingFallback: boolean;
}): Promise<{ ok: true; paths: string[] } | { ok: false; error: string }> {
const rootDir = path.resolve(params.rootDir);
let rootRealPath: string | undefined;
try {
rootRealPath = await fs.realpath(rootDir);
} catch {
// Keep historical behavior for missing roots and rely on openFileWithinRoot for final checks.
rootRealPath = undefined;
}
// Keep historical behavior for missing roots and rely on openFileWithinRoot for final checks.
const rootRealPath = await resolveRealPathIfExists(rootDir);
const isInRoot = (relativePath: string) =>
Boolean(relativePath) && !relativePath.startsWith("..") && !path.isAbsolute(relativePath);