mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 10:21:24 +00:00
fix(security): harden archive extraction (#16203)
* fix(browser): confine upload paths for file chooser * fix(browser): sanitize suggested download filenames * chore(lint): avoid control regex in download sanitizer * test(browser): cover absolute escape paths * docs(browser): update upload example path * refactor(browser): centralize upload path confinement * fix(infra): harden tmp dir selection * fix(security): harden archive extraction * fix(infra): harden tar extraction filter
This commit is contained in:
committed by
GitHub
parent
9a134c8a10
commit
3aa94afcfd
@@ -6,7 +6,14 @@ export const POSIX_OPENCLAW_TMP_DIR = "/tmp/openclaw";
|
||||
|
||||
type ResolvePreferredOpenClawTmpDirOptions = {
|
||||
accessSync?: (path: string, mode?: number) => void;
|
||||
statSync?: (path: string) => { isDirectory(): boolean };
|
||||
lstatSync?: (path: string) => {
|
||||
isDirectory(): boolean;
|
||||
isSymbolicLink(): boolean;
|
||||
mode?: number;
|
||||
uid?: number;
|
||||
};
|
||||
mkdirSync?: (path: string, opts: { recursive: boolean; mode?: number }) => void;
|
||||
getuid?: () => number | undefined;
|
||||
tmpdir?: () => string;
|
||||
};
|
||||
|
||||
@@ -25,26 +32,73 @@ export function resolvePreferredOpenClawTmpDir(
|
||||
options: ResolvePreferredOpenClawTmpDirOptions = {},
|
||||
): string {
|
||||
const accessSync = options.accessSync ?? fs.accessSync;
|
||||
const statSync = options.statSync ?? fs.statSync;
|
||||
const lstatSync = options.lstatSync ?? fs.lstatSync;
|
||||
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
||||
const getuid =
|
||||
options.getuid ??
|
||||
(() => {
|
||||
try {
|
||||
return typeof process.getuid === "function" ? process.getuid() : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
const tmpdir = options.tmpdir ?? os.tmpdir;
|
||||
const uid = getuid();
|
||||
|
||||
const isSecureDirForUser = (st: { mode?: number; uid?: number }): boolean => {
|
||||
if (uid === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (typeof st.uid === "number" && st.uid !== uid) {
|
||||
return false;
|
||||
}
|
||||
// Avoid group/other writable dirs when running on multi-user hosts.
|
||||
if (typeof st.mode === "number" && (st.mode & 0o022) !== 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const fallback = (): string => {
|
||||
const base = tmpdir();
|
||||
const suffix = uid === undefined ? "openclaw" : `openclaw-${uid}`;
|
||||
return path.join(base, suffix);
|
||||
};
|
||||
|
||||
try {
|
||||
const preferred = statSync(POSIX_OPENCLAW_TMP_DIR);
|
||||
if (!preferred.isDirectory()) {
|
||||
return path.join(tmpdir(), "openclaw");
|
||||
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();
|
||||
}
|
||||
return POSIX_OPENCLAW_TMP_DIR;
|
||||
} catch (err) {
|
||||
if (!isNodeErrorWithCode(err, "ENOENT")) {
|
||||
return path.join(tmpdir(), "openclaw");
|
||||
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 {
|
||||
return fallback();
|
||||
}
|
||||
return POSIX_OPENCLAW_TMP_DIR;
|
||||
} catch {
|
||||
return path.join(tmpdir(), "openclaw");
|
||||
return fallback();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user