mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-13 12:20:35 +00:00
fix: harden sandbox writes and centralize atomic file writes
This commit is contained in:
@@ -14,23 +14,45 @@ export async function readJsonFile<T>(filePath: string): Promise<T | null> {
|
||||
export async function writeJsonAtomic(
|
||||
filePath: string,
|
||||
value: unknown,
|
||||
options?: { mode?: number },
|
||||
options?: { mode?: number; trailingNewline?: boolean; ensureDirMode?: number },
|
||||
) {
|
||||
const text = JSON.stringify(value, null, 2);
|
||||
await writeTextAtomic(filePath, text, {
|
||||
mode: options?.mode,
|
||||
ensureDirMode: options?.ensureDirMode,
|
||||
appendTrailingNewline: options?.trailingNewline,
|
||||
});
|
||||
}
|
||||
|
||||
export async function writeTextAtomic(
|
||||
filePath: string,
|
||||
content: string,
|
||||
options?: { mode?: number; ensureDirMode?: number; appendTrailingNewline?: boolean },
|
||||
) {
|
||||
const mode = options?.mode ?? 0o600;
|
||||
const dir = path.dirname(filePath);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
const tmp = `${filePath}.${randomUUID()}.tmp`;
|
||||
await fs.writeFile(tmp, JSON.stringify(value, null, 2), "utf8");
|
||||
try {
|
||||
await fs.chmod(tmp, mode);
|
||||
} catch {
|
||||
// best-effort; ignore on platforms without chmod
|
||||
const payload =
|
||||
options?.appendTrailingNewline && !content.endsWith("\n") ? `${content}\n` : content;
|
||||
const mkdirOptions: { recursive: true; mode?: number } = { recursive: true };
|
||||
if (typeof options?.ensureDirMode === "number") {
|
||||
mkdirOptions.mode = options.ensureDirMode;
|
||||
}
|
||||
await fs.rename(tmp, filePath);
|
||||
await fs.mkdir(path.dirname(filePath), mkdirOptions);
|
||||
const tmp = `${filePath}.${randomUUID()}.tmp`;
|
||||
try {
|
||||
await fs.chmod(filePath, mode);
|
||||
} catch {
|
||||
// best-effort; ignore on platforms without chmod
|
||||
await fs.writeFile(tmp, payload, "utf8");
|
||||
try {
|
||||
await fs.chmod(tmp, mode);
|
||||
} catch {
|
||||
// best-effort; ignore on platforms without chmod
|
||||
}
|
||||
await fs.rename(tmp, filePath);
|
||||
try {
|
||||
await fs.chmod(filePath, mode);
|
||||
} catch {
|
||||
// best-effort; ignore on platforms without chmod
|
||||
}
|
||||
} finally {
|
||||
await fs.rm(tmp, { force: true }).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { loadConfig } from "../config/config.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { writeJsonAtomic } from "./json-files.js";
|
||||
import { resolveOpenClawPackageRoot } from "./openclaw-root.js";
|
||||
import { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from "./update-channels.js";
|
||||
import { compareSemverStrings, resolveNpmChannelTag, checkUpdateStatus } from "./update-check.js";
|
||||
@@ -124,8 +125,7 @@ async function readState(statePath: string): Promise<UpdateCheckState> {
|
||||
}
|
||||
|
||||
async function writeState(statePath: string, state: UpdateCheckState): Promise<void> {
|
||||
await fs.mkdir(path.dirname(statePath), { recursive: true });
|
||||
await fs.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
|
||||
await writeJsonAtomic(statePath, state);
|
||||
}
|
||||
|
||||
function sameUpdateAvailable(a: UpdateAvailable | null, b: UpdateAvailable | null): boolean {
|
||||
|
||||
Reference in New Issue
Block a user