mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 19:38:28 +00:00
fix(security): harden browser trace/download temp path handling
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { SafeOpenError, openFileWithinRoot } from "../infra/fs-safe.js";
|
||||
import { isNotFoundPathError, isPathInside } from "../infra/path-guards.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||
|
||||
export const DEFAULT_BROWSER_TMP_DIR = resolvePreferredOpenClawTmpDir();
|
||||
@@ -30,6 +31,67 @@ export function resolvePathWithinRoot(params: {
|
||||
return { ok: true, path: resolved };
|
||||
}
|
||||
|
||||
export async function resolveWritablePathWithinRoot(params: {
|
||||
rootDir: string;
|
||||
requestedPath: string;
|
||||
scopeLabel: string;
|
||||
defaultFileName?: string;
|
||||
}): Promise<{ ok: true; path: string } | { ok: false; error: string }> {
|
||||
const lexical = resolvePathWithinRoot(params);
|
||||
if (!lexical.ok) {
|
||||
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 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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
return lexical;
|
||||
}
|
||||
|
||||
export function resolvePathsWithinRoot(params: {
|
||||
rootDir: string;
|
||||
requestedPaths: string[];
|
||||
|
||||
Reference in New Issue
Block a user