harden: defense-in-depth on vote/signup/area-lead boundaries

Audit follow-up (RLS already enforced these; adding boundary checks so a
future RLS regression cannot silently allow cross-space writes):
- castVote: verify the proposal is in the caller's space and still open
  before the upsert.
- cancelMySignup: scope every admin-client query/update in the waitlist
  promotion path by member.space_id (not session_id alone).
- setAreaLead: reject a lead_id that is not a member of the space.
- signUpForClass: comment the space-safety invariant on the count.
No behavior change for valid callers.
This commit is contained in:
Moheeb Zara
2026-05-16 22:21:01 -07:00
parent e125f52ca6
commit dd9e3aced6
3 changed files with 36 additions and 1 deletions
+13
View File
@@ -151,6 +151,19 @@ export async function castVote(formData: {
if (!auth.ok) return { error: auth.error }
const { member } = auth
// Defense in depth: the votes RLS policies already enforce same-space,
// open status and the voting window, but this is a sensitive mutation, so
// validate at the action boundary too (a future RLS regression must not
// silently allow cross-space vote injection).
const { data: proposal } = await supabase
.from('proposals')
.select('id, status')
.eq('id', v.data.proposalId)
.eq('space_id', member.space_id)
.maybeSingle()
if (!proposal) return { error: 'Proposal not found' }
if (proposal.status !== 'open') return { error: 'Voting is closed for this proposal.' }
const { error } = await supabase
.from('proposal_votes')
.upsert(