refactor(tmp): simplify trusted tmp dir state checks

This commit is contained in:
Peter Steinberger
2026-02-26 02:46:43 +01:00
parent acbb93be48
commit 1f004e6640

View File

@@ -3,6 +3,7 @@ import os from "node:os";
import path from "node:path"; import path from "node:path";
export const POSIX_OPENCLAW_TMP_DIR = "/tmp/openclaw"; export const POSIX_OPENCLAW_TMP_DIR = "/tmp/openclaw";
const TMP_DIR_ACCESS_MODE = fs.constants.W_OK | fs.constants.X_OK;
type ResolvePreferredOpenClawTmpDirOptions = { type ResolvePreferredOpenClawTmpDirOptions = {
accessSync?: (path: string, mode?: number) => void; accessSync?: (path: string, mode?: number) => void;
@@ -66,7 +67,7 @@ export function resolvePreferredOpenClawTmpDir(
return path.join(base, suffix); return path.join(base, suffix);
}; };
const isTrustedPreferredDir = (st: { const isTrustedTmpDir = (st: {
isDirectory(): boolean; isDirectory(): boolean;
isSymbolicLink(): boolean; isSymbolicLink(): boolean;
mode?: number; mode?: number;
@@ -75,18 +76,13 @@ export function resolvePreferredOpenClawTmpDir(
return st.isDirectory() && !st.isSymbolicLink() && isSecureDirForUser(st); return st.isDirectory() && !st.isSymbolicLink() && isSecureDirForUser(st);
}; };
const resolveDirState = ( const resolveDirState = (candidatePath: string): "available" | "missing" | "invalid" => {
candidatePath: string,
requireWritableAccess: boolean,
): "available" | "missing" | "invalid" => {
try { try {
const candidate = lstatSync(candidatePath); const candidate = lstatSync(candidatePath);
if (!isTrustedPreferredDir(candidate)) { if (!isTrustedTmpDir(candidate)) {
return "invalid"; return "invalid";
} }
if (requireWritableAccess) { accessSync(candidatePath, TMP_DIR_ACCESS_MODE);
accessSync(candidatePath, fs.constants.W_OK | fs.constants.X_OK);
}
return "available"; return "available";
} catch (err) { } catch (err) {
if (isNodeErrorWithCode(err, "ENOENT")) { if (isNodeErrorWithCode(err, "ENOENT")) {
@@ -98,7 +94,7 @@ export function resolvePreferredOpenClawTmpDir(
const ensureTrustedFallbackDir = (): string => { const ensureTrustedFallbackDir = (): string => {
const fallbackPath = fallback(); const fallbackPath = fallback();
const state = resolveDirState(fallbackPath, true); const state = resolveDirState(fallbackPath);
if (state === "available") { if (state === "available") {
return fallbackPath; return fallbackPath;
} }
@@ -110,13 +106,13 @@ export function resolvePreferredOpenClawTmpDir(
} catch { } catch {
throw new Error(`Unable to create fallback OpenClaw temp dir: ${fallbackPath}`); throw new Error(`Unable to create fallback OpenClaw temp dir: ${fallbackPath}`);
} }
if (resolveDirState(fallbackPath, true) !== "available") { if (resolveDirState(fallbackPath) !== "available") {
throw new Error(`Unsafe fallback OpenClaw temp dir: ${fallbackPath}`); throw new Error(`Unsafe fallback OpenClaw temp dir: ${fallbackPath}`);
} }
return fallbackPath; return fallbackPath;
}; };
const existingPreferredState = resolveDirState(POSIX_OPENCLAW_TMP_DIR, true); const existingPreferredState = resolveDirState(POSIX_OPENCLAW_TMP_DIR);
if (existingPreferredState === "available") { if (existingPreferredState === "available") {
return POSIX_OPENCLAW_TMP_DIR; return POSIX_OPENCLAW_TMP_DIR;
} }
@@ -125,10 +121,10 @@ export function resolvePreferredOpenClawTmpDir(
} }
try { try {
accessSync("/tmp", fs.constants.W_OK | fs.constants.X_OK); accessSync("/tmp", TMP_DIR_ACCESS_MODE);
// Create with a safe default; subsequent callers expect it exists. // Create with a safe default; subsequent callers expect it exists.
mkdirSync(POSIX_OPENCLAW_TMP_DIR, { recursive: true, mode: 0o700 }); mkdirSync(POSIX_OPENCLAW_TMP_DIR, { recursive: true, mode: 0o700 });
if (resolveDirState(POSIX_OPENCLAW_TMP_DIR, true) !== "available") { if (resolveDirState(POSIX_OPENCLAW_TMP_DIR) !== "available") {
return ensureTrustedFallbackDir(); return ensureTrustedFallbackDir();
} }
return POSIX_OPENCLAW_TMP_DIR; return POSIX_OPENCLAW_TMP_DIR;