diff --git a/src/relay/cursor-sidecar-scan.test.ts b/src/relay/cursor-sidecar-scan.test.ts index 16ac0e5d0a2..491109aa179 100644 --- a/src/relay/cursor-sidecar-scan.test.ts +++ b/src/relay/cursor-sidecar-scan.test.ts @@ -313,6 +313,30 @@ describe('scanCursorSidecars', () => { ) }) + it('isolates response-invalid file timestamps without rejecting other sidecars', async () => { + const root = await createRoot() + const chatsRoot = join(root, 'chats') + const bucket = 'acacacacacacacacacacacacacacacac' + await Promise.all([ + addSession(chatsRoot, bucket, 'invalid-time'), + addSession(chatsRoot, bucket, 'valid') + ]) + await setSessionMtime(chatsRoot, bucket, 'invalid-time', -1_000) + + const result = await scanCursorSidecars( + defaultCursorSidecarScanRequest(chatsRoot, [], process.platform), + context + ) + + expect(result.sidecars.map((sidecar) => sidecar.sessionId)).toEqual(['valid']) + expect(result.issues).toContainEqual( + expect.objectContaining({ + path: join(chatsRoot, bucket, 'invalid-time', 'meta.json'), + message: 'Cursor session metadata has invalid file timestamps.' + }) + ) + }) + it('normalizes scope truncation deterministically inside the owning-host scan', async () => { const root = await createRoot() const chatsRoot = join(root, 'chats') diff --git a/src/shared/cursor-sidecar-scan-discovery.ts b/src/shared/cursor-sidecar-scan-discovery.ts index 73bec4e896a..3a1e50ae669 100644 --- a/src/shared/cursor-sidecar-scan-discovery.ts +++ b/src/shared/cursor-sidecar-scan-discovery.ts @@ -192,6 +192,18 @@ async function inspectSession( if (!meta.isFile() || meta.isSymbolicLink() || !store.isFile() || store.isSymbolicLink()) { return null } + if (!isFiniteNonnegative(meta.mtimeMs) || !isFiniteNonnegative(store.mtimeMs)) { + addIssue( + args.response, + metaPath, + new Error('Cursor session metadata has invalid file timestamps.') + ) + return null + } + if (!isFiniteNonnegative(meta.size)) { + addIssue(args.response, metaPath, new Error('Cursor session metadata has an invalid size.')) + return null + } if (meta.size > args.caps.sidecarBytes) { addIssue( args.response, @@ -277,3 +289,7 @@ function isMissing(error: unknown): boolean { const code = (error as NodeJS.ErrnoException | null)?.code return code === 'ENOENT' || code === 'ENOTDIR' } + +function isFiniteNonnegative(value: number): boolean { + return Number.isFinite(value) && value >= 0 +}