fix: scope GRANT statements to match actual RLS-permitted access per role

Replace the blanket select/insert/update/delete grant to anon/authenticated/
service_role with privilege-scoped grants: anon/authenticated get select-only
on the public-read tables plus insert-only on bookings/inquiries; full CRUD
is reserved for authenticated (narrowed to the admin UID by RLS) and
service_role. Table grants are now a real second layer of defense instead of
mirroring RLS. Updates two pgTAP assertions whose blocking mechanism moved
from RLS-filtered-empty to grant-level permission-denied, and adds a new
assertion proving anon cannot UPDATE bookings.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
This commit is contained in:
belviskhoremk
2026-08-08 13:08:55 +00:00
parent 5e3a77fc70
commit aacb9dca12
2 changed files with 47 additions and 8 deletions

View File

@@ -3,8 +3,32 @@
-- anon/authenticated/service_role, so without these GRANTs the RLS policies
-- from the initial schema migration are unreachable (every request fails at
-- the privilege check before RLS is even evaluated). This is the documented
-- "explicit grants + RLS" pattern Supabase recommends going forward; the
-- actual access is still fully governed by each table's RLS policies.
-- "explicit grants + RLS" pattern Supabase recommends going forward.
--
-- Unlike the previous blanket grant, these privileges are scoped to match
-- what each role's RLS policies actually permit, so table-level GRANTs are a
-- real second layer of defense rather than a no-op rubber stamp on top of
-- RLS: anon/authenticated can only select the public-read tables and insert
-- into bookings/inquiries; only authenticated (narrowed further by RLS's
-- admin-only policies to the single admin UID) gets full read/write on
-- everything. service_role still needs an explicit full grant here: table
-- privileges and RLS are two independent checks in Postgres — service_role's
-- BYPASSRLS role attribute only skips the RLS policy check, it does not
-- substitute for the base GRANT privilege check, so without this it cannot
-- touch these tables at all (e.g. the test suite's service_role seed step).
grant select on
public.products,
public.services,
public.schedule_template,
public.schedule_exceptions,
public.booked_slots
to anon, authenticated;
grant insert on
public.bookings,
public.inquiries
to anon, authenticated;
grant select, insert, update, delete on
public.products,
public.services,
@@ -13,7 +37,7 @@ grant select, insert, update, delete on
public.bookings,
public.booked_slots,
public.inquiries
to anon, authenticated, service_role;
to authenticated, service_role;
-- Keeps the public, PII-free booked_slots table in sync whenever a booking
-- is created. security definer + explicit search_path per Supabase's