Gateway: harden cron.runs jobId path handling (openclaw#24038) thanks @Takhoffman

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Tak Hoffman
2026-02-22 19:35:26 -06:00
committed by GitHub
parent 45febecf2a
commit 259d863353
6 changed files with 79 additions and 6 deletions

View File

@@ -25,6 +25,19 @@ describe("cron run log", () => {
expect(p.endsWith(path.join(os.tmpdir(), "cron", "runs", "job-1.jsonl"))).toBe(true);
});
it("rejects unsafe job ids when resolving run log path", () => {
const storePath = path.join(os.tmpdir(), "cron", "jobs.json");
expect(() => resolveCronRunLogPath({ storePath, jobId: "../job-1" })).toThrow(
/invalid cron run log job id/i,
);
expect(() => resolveCronRunLogPath({ storePath, jobId: "nested/job-1" })).toThrow(
/invalid cron run log job id/i,
);
expect(() => resolveCronRunLogPath({ storePath, jobId: "..\\job-1" })).toThrow(
/invalid cron run log job id/i,
);
});
it("appends JSONL and prunes by line count", async () => {
await withRunLogDir("openclaw-cron-log-", async (dir) => {
const logPath = path.join(dir, "runs", "job-1.jsonl");

View File

@@ -19,10 +19,27 @@ export type CronRunLogEntry = {
nextRunAtMs?: number;
} & CronRunTelemetry;
function assertSafeCronRunLogJobId(jobId: string): string {
const trimmed = jobId.trim();
if (!trimmed) {
throw new Error("invalid cron run log job id");
}
if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("\0")) {
throw new Error("invalid cron run log job id");
}
return trimmed;
}
export function resolveCronRunLogPath(params: { storePath: string; jobId: string }) {
const storePath = path.resolve(params.storePath);
const dir = path.dirname(storePath);
return path.join(dir, "runs", `${params.jobId}.jsonl`);
const runsDir = path.resolve(dir, "runs");
const safeJobId = assertSafeCronRunLogJobId(params.jobId);
const resolvedPath = path.resolve(runsDir, `${safeJobId}.jsonl`);
if (!resolvedPath.startsWith(`${runsDir}${path.sep}`)) {
throw new Error("invalid cron run log job id");
}
return resolvedPath;
}
const writesByPath = new Map<string, Promise<void>>();