From 00777077c280bbee566487bb956f45b7f1074194 Mon Sep 17 00:00:00 2001 From: aarbit Date: Mon, 28 Sep 2026 09:42:36 -0500 Subject: [PATCH] Grant table privileges to anon/authenticated/service_role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RLS policies alone don't grant table-level access in Postgres — missing GRANTs caused "permission denied for table profiles" in production even though the RLS policies were correct. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x --- .../20260927000000_grant_table_privileges.sql | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 supabase/migrations/20260927000000_grant_table_privileges.sql diff --git a/supabase/migrations/20260927000000_grant_table_privileges.sql b/supabase/migrations/20260927000000_grant_table_privileges.sql new file mode 100644 index 0000000..340740a --- /dev/null +++ b/supabase/migrations/20260927000000_grant_table_privileges.sql @@ -0,0 +1,31 @@ +-- Fixes a production-only bug: every table in this schema was created with +-- RLS policies but no Postgres-level GRANTs, which local self-hosted +-- Supabase's Docker bootstrap happens to pre-grant broadly on the `public` +-- schema — masking the gap in every local dev/test session so far — but a +-- fresh Supabase Cloud project does not. Confirmed against Supabase's own +-- docs (supabase.com/docs/guides/auth/managing-user-data): RLS policies +-- only ever narrow access an underlying GRANT already allows; with no +-- GRANT, every request from the `anon`/`authenticated` roles fails at the +-- Postgres level before RLS is even evaluated ("permission denied for +-- table ..."), regardless of how permissive the policy is. First surfaced +-- as CompleteProfileScreen's username-save failing after Google sign-in on +-- app.diagrav.com. +-- +-- `authenticated`/`service_role` get full CRUD per table — RLS (already +-- written and unchanged by this migration) is what actually narrows each +-- operation to the right rows. `anon` gets SELECT only, matching Supabase's +-- own least-privilege example; nothing in this app reads `public` schema +-- tables before sign-in today, but it's a harmless, RLS-gated default to +-- have in place rather than an under-grant that surfaces as another opaque +-- "permission denied" later. +-- +-- The `alter default privileges` statements make this automatic for tables +-- any future migration creates, so this class of bug can't recur. + +grant usage on schema public to anon, authenticated, service_role; + +grant select on all tables in schema public to anon; +grant select, insert, update, delete on all tables in schema public to authenticated, service_role; + +alter default privileges in schema public grant select on tables to anon; +alter default privileges in schema public grant select, insert, update, delete on tables to authenticated, service_role;