fix(security): scope checkpoint routes to session — close 2 IDORs (v2.7.2)
Flagged by the automated push security review on v2.7.1. - GET /checkpoints?chat_id= : the chat_id branch filtered by chat_id alone (any session's chat_id read its checkpoints). Now joins chats and gates on chats.session_id. - restoreCheckpoint scope guard was fail-open: `cp.session_id && cp.session_id !== sessionId` fell through on a null denormalized session_id, allowing a cross-session restore (worktree reset + transcript trim). Now resolves the owning session via the checkpoint's chat and denies on missing/mismatch. - Adds a DB-integration regression for the null-session_id cross-session case. Both scope authoritatively through chats.session_id (checkpoints.session_id is a nullable hint). Coder suite 234 passing; 7/7 checkpoint tests (incl. the regression) against live postgres+git; typecheck clean. Hotfix on v2.7.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -233,4 +233,20 @@ describe.runIf(!!process.env.DATABASE_URL)('checkpoint create + restore (DB + gi
|
||||
).rejects.toBeInstanceOf(CheckpointNotFoundError);
|
||||
await sql`DELETE FROM checkpoints WHERE id = ${cp!.id}`;
|
||||
});
|
||||
|
||||
it('restoreCheckpoint denies a NULL-session_id checkpoint from another session (no fail-open IDOR)', async () => {
|
||||
// Regression for the fail-open authorization bug: a checkpoint row whose
|
||||
// denormalized session_id is NULL must STILL be scoped via its chat's owning
|
||||
// session (chats.session_id), not skipped. The old guard `cp.session_id &&
|
||||
// cp.session_id !== sessionId` fell through on NULL → cross-session restore.
|
||||
const [row] = await sql<{ id: string }[]>`
|
||||
INSERT INTO checkpoints (chat_id, session_id, message_id, commit_sha)
|
||||
VALUES (${chatId}, NULL, NULL, 'deadbeef')
|
||||
RETURNING id
|
||||
`;
|
||||
await expect(
|
||||
restoreCheckpoint(sql, row!.id, { sessionId: '22222222-2222-2222-2222-222222222222' }),
|
||||
).rejects.toBeInstanceOf(CheckpointNotFoundError);
|
||||
await sql`DELETE FROM checkpoints WHERE id = ${row!.id}`;
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user