fix(04-07): add owner-only guard for isShared on PATCH /api/lists/:id (T-04-08)

- Immediately after access check, return 403 if patch.isShared !== undefined
  and !access.isOwner — blocks sharees from mutating list_shares
- Guard message: 'Only the list owner can change sharing settings'
- Sharees may still PATCH { name } (rename test stays green)
- Update stale comment: 'Reconcile list_shares on visibility change (owner only)'
- Closes T-04-08 (elevation of privilege) and T-04-05 (shared root cause)
This commit is contained in:
Lucas Berger
2026-06-09 14:22:48 -04:00
parent 931f767922
commit c0bd6d732d
+7 -1
View File
@@ -331,6 +331,12 @@ listsRouter.patch('/:id', zValidator('json', patchListSchema), async (c) => {
return c.json({ error: 'Access denied' }, 403)
}
// T-04-08 / T-04-05: owner-only guard for isShared mutations.
// A sharee may rename a list (patch.name) but must never mutate list_shares.
if (patch.isShared !== undefined && !access.isOwner) {
return c.json({ error: 'Only the list owner can change sharing settings' }, 403)
}
const prevIsShared = access.listRow.isShared
const newIsShared = patch.isShared ?? prevIsShared
@@ -341,7 +347,7 @@ listsRouter.patch('/:id', zValidator('json', patchListSchema), async (c) => {
await db.update(lists).set(updateValues).where(eq(lists.id, listId))
// Reconcile list_shares on visibility change (owner only affects shares)
// Owner-only: reconcile list_shares on visibility change
if (patch.isShared !== undefined && patch.isShared !== prevIsShared) {
if (newIsShared) {
// false → true: insert shares for all other members (except owner)