Skip to content

Commit 6e18c8e

Browse files
fix(cron): use guarded root append for run logs
1 parent f0b4024 commit 6e18c8e

1 file changed

Lines changed: 21 additions & 6 deletions

File tree

src/cron/run-log.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import fs from "node:fs/promises";
33
import path from "node:path";
44
import { parseByteSize } from "../cli/parse-bytes.js";
55
import type { CronConfig } from "../config/types.cron.js";
6-
import { appendRegularFile, isPathInside, pathExists, root as fsRoot } from "../infra/fs-safe.js";
6+
import { assertNoSymlinkParents } from "../infra/fs-safe-advanced.js";
7+
import { isPathInside, pathExists, root as fsRoot } from "../infra/fs-safe.js";
78
import { privateFileStore } from "../infra/private-file-store.js";
89
import {
910
normalizeLowercaseStringOrEmpty,
@@ -154,6 +155,24 @@ async function pruneIfNeeded(filePath: string, opts: { maxBytes: number; keepLin
154155
);
155156
}
156157

158+
async function appendRunLogLine(filePath: string, content: string) {
159+
const runDir = path.dirname(filePath);
160+
await assertNoSymlinkParents({
161+
rootDir: path.parse(runDir).root,
162+
targetPath: runDir,
163+
allowMissing: false,
164+
allowRootChildSymlink: true,
165+
requireDirectories: true,
166+
messagePrefix: "Refusing to append under",
167+
});
168+
await (
169+
await fsRoot(runDir, { mode: 0o600 })
170+
).append(path.basename(filePath), content, {
171+
mkdir: false,
172+
mode: 0o600,
173+
});
174+
}
175+
157176
export async function appendCronRunLog(
158177
filePath: string,
159178
entry: CronRunLogEntry,
@@ -167,11 +186,7 @@ export async function appendCronRunLog(
167186
const runDir = path.dirname(resolved);
168187
await fs.mkdir(runDir, { recursive: true, mode: 0o700 });
169188
await fs.chmod(runDir, 0o700).catch(() => undefined);
170-
await appendRegularFile({
171-
filePath: resolved,
172-
content: `${JSON.stringify(entry)}\n`,
173-
rejectSymlinkParents: true,
174-
});
189+
await appendRunLogLine(resolved, `${JSON.stringify(entry)}\n`);
175190
await setSecureFileMode(resolved);
176191
await pruneIfNeeded(resolved, {
177192
maxBytes: opts?.maxBytes ?? DEFAULT_CRON_RUN_LOG_MAX_BYTES,

0 commit comments

Comments
 (0)