mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 18:21:23 +00:00
refactor(tmp): harden temp boundary guardrails
This commit is contained in:
@@ -66,35 +66,48 @@ export function resolvePreferredOpenClawTmpDir(
|
||||
return path.join(base, suffix);
|
||||
};
|
||||
|
||||
try {
|
||||
const preferred = lstatSync(POSIX_OPENCLAW_TMP_DIR);
|
||||
if (!preferred.isDirectory() || preferred.isSymbolicLink()) {
|
||||
return fallback();
|
||||
}
|
||||
accessSync(POSIX_OPENCLAW_TMP_DIR, fs.constants.W_OK | fs.constants.X_OK);
|
||||
if (!isSecureDirForUser(preferred)) {
|
||||
return fallback();
|
||||
const isTrustedPreferredDir = (st: {
|
||||
isDirectory(): boolean;
|
||||
isSymbolicLink(): boolean;
|
||||
mode?: number;
|
||||
uid?: number;
|
||||
}): boolean => {
|
||||
return st.isDirectory() && !st.isSymbolicLink() && isSecureDirForUser(st);
|
||||
};
|
||||
|
||||
const resolvePreferredState = (
|
||||
requireWritableAccess: boolean,
|
||||
): "available" | "missing" | "invalid" => {
|
||||
try {
|
||||
const preferred = lstatSync(POSIX_OPENCLAW_TMP_DIR);
|
||||
if (!isTrustedPreferredDir(preferred)) {
|
||||
return "invalid";
|
||||
}
|
||||
if (requireWritableAccess) {
|
||||
accessSync(POSIX_OPENCLAW_TMP_DIR, fs.constants.W_OK | fs.constants.X_OK);
|
||||
}
|
||||
return "available";
|
||||
} catch (err) {
|
||||
if (isNodeErrorWithCode(err, "ENOENT")) {
|
||||
return "missing";
|
||||
}
|
||||
return "invalid";
|
||||
}
|
||||
};
|
||||
|
||||
const existingPreferredState = resolvePreferredState(true);
|
||||
if (existingPreferredState === "available") {
|
||||
return POSIX_OPENCLAW_TMP_DIR;
|
||||
} catch (err) {
|
||||
if (!isNodeErrorWithCode(err, "ENOENT")) {
|
||||
return fallback();
|
||||
}
|
||||
}
|
||||
if (existingPreferredState === "invalid") {
|
||||
return fallback();
|
||||
}
|
||||
|
||||
try {
|
||||
accessSync("/tmp", fs.constants.W_OK | fs.constants.X_OK);
|
||||
// Create with a safe default; subsequent callers expect it exists.
|
||||
mkdirSync(POSIX_OPENCLAW_TMP_DIR, { recursive: true, mode: 0o700 });
|
||||
try {
|
||||
const preferred = lstatSync(POSIX_OPENCLAW_TMP_DIR);
|
||||
if (!preferred.isDirectory() || preferred.isSymbolicLink()) {
|
||||
return fallback();
|
||||
}
|
||||
if (!isSecureDirForUser(preferred)) {
|
||||
return fallback();
|
||||
}
|
||||
} catch {
|
||||
if (resolvePreferredState(true) !== "available") {
|
||||
return fallback();
|
||||
}
|
||||
return POSIX_OPENCLAW_TMP_DIR;
|
||||
|
||||
@@ -4,9 +4,25 @@ import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||
|
||||
function buildMediaLocalRoots(stateDir: string): string[] {
|
||||
type BuildMediaLocalRootsOptions = {
|
||||
preferredTmpDir?: string;
|
||||
};
|
||||
|
||||
let cachedPreferredTmpDir: string | undefined;
|
||||
|
||||
function resolveCachedPreferredTmpDir(): string {
|
||||
if (!cachedPreferredTmpDir) {
|
||||
cachedPreferredTmpDir = resolvePreferredOpenClawTmpDir();
|
||||
}
|
||||
return cachedPreferredTmpDir;
|
||||
}
|
||||
|
||||
function buildMediaLocalRoots(
|
||||
stateDir: string,
|
||||
options: BuildMediaLocalRootsOptions = {},
|
||||
): string[] {
|
||||
const resolvedStateDir = path.resolve(stateDir);
|
||||
const preferredTmpDir = resolvePreferredOpenClawTmpDir();
|
||||
const preferredTmpDir = options.preferredTmpDir ?? resolveCachedPreferredTmpDir();
|
||||
return [
|
||||
preferredTmpDir,
|
||||
path.join(resolvedStateDir, "media"),
|
||||
|
||||
@@ -31,6 +31,15 @@ function resolveTempRoot(tmpDir?: string): string {
|
||||
return tmpDir ?? resolvePreferredOpenClawTmpDir();
|
||||
}
|
||||
|
||||
function isNodeErrorWithCode(err: unknown, code: string): boolean {
|
||||
return (
|
||||
typeof err === "object" &&
|
||||
err !== null &&
|
||||
"code" in err &&
|
||||
(err as { code?: string }).code === code
|
||||
);
|
||||
}
|
||||
|
||||
export function buildRandomTempFilePath(params: {
|
||||
prefix: string;
|
||||
extension?: string;
|
||||
@@ -64,6 +73,12 @@ export async function withTempDownloadPath<T>(
|
||||
try {
|
||||
return await fn(tmpPath);
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true }).catch(() => {});
|
||||
try {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
} catch (err) {
|
||||
if (!isNodeErrorWithCode(err, "ENOENT")) {
|
||||
console.warn(`temp-path cleanup failed for ${dir}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user