fix: harden sandbox writes and centralize atomic file writes

This commit is contained in:
Peter Steinberger
2026-03-02 16:44:46 +00:00
parent 14e4575af5
commit 18f8393b6c
12 changed files with 203 additions and 139 deletions

View File

@@ -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);
}
}