diff --git a/src/infra/restart-handoff.test.ts b/src/infra/restart-handoff.test.ts index a0b6a5a4728..f87df4c2ce4 100644 --- a/src/infra/restart-handoff.test.ts +++ b/src/infra/restart-handoff.test.ts @@ -190,6 +190,36 @@ describe("gateway restart handoff", () => { expect(fs.existsSync(handoffPath(env))).toBe(false); }); + it("rejects persisted handoffs with a ttl longer than the supported window", () => { + const env = createHandoffEnv(); + + fs.writeFileSync( + handoffPath(env), + `${JSON.stringify({ + kind: GATEWAY_SUPERVISOR_RESTART_HANDOFF_KIND, + version: 1, + intentId: "too-long", + pid: 111, + createdAt: 1_000, + expiresAt: 61_001, + source: "plugin-change", + restartKind: "full-process", + supervisorMode: "external", + })}\n`, + { encoding: "utf8", mode: 0o600 }, + ); + + expect(readGatewayRestartHandoffSync(env, 1_001)).toBeNull(); + expect( + consumeGatewayRestartHandoffForExitedProcessSync({ + env, + exitedPid: 111, + now: 1_001, + }), + ).toBeNull(); + expect(fs.existsSync(handoffPath(env))).toBe(false); + }); + it("does not follow an existing handoff-path symlink when writing", () => { const env = createHandoffEnv(); const targetPath = path.join(env.OPENCLAW_STATE_DIR ?? "", "attacker-target.txt"); diff --git a/src/infra/restart-handoff.ts b/src/infra/restart-handoff.ts index 56970b7fd26..7d4ea850e81 100644 --- a/src/infra/restart-handoff.ts +++ b/src/infra/restart-handoff.ts @@ -157,6 +157,7 @@ function parseGatewayRestartHandoff(raw: string): GatewayRestartHandoff | null { typeof parsed.expiresAt !== "number" || !Number.isFinite(parsed.expiresAt) || parsed.expiresAt <= parsed.createdAt || + parsed.expiresAt - parsed.createdAt > GATEWAY_RESTART_HANDOFF_TTL_MS || !isSource(parsed.source) || !isRestartKind(parsed.restartKind) || !isSupervisorMode(parsed.supervisorMode)