Replies: 2 comments
|
So judging by this https://supabase.com/docs/guides/database/postgres/column-level-security this is expected behaviour. You can set table level privileges and column level and the more general privilege always succeeds so it makes sense that granting select on public.credentials would lead to mean every column succeeds. Back to your initial issue about why insert fails, sometimes you also need a select when doing an insert, this guide explains it better https://supabase.com/docs/guides/troubleshooting/storage-error-403-forbidden-new-row-violates-row-level-security-policy-on-upload-a94384 but this make be the reason you are seeing some issues now. What does your javascript look like at the moment? |
|
Two separate things are happening here, and the first one is not a bug. Postgres has no negative grants. The same asymmetry bites in the other direction: a column-level revoke issued while the role still holds table-level SELECT does nothing at all, and Postgres raises no error when you run it. That silent no-op is the part that catches people. The only shape that works is to drop the table-level grant first: revoke select on public.credentials from anon, authenticated;
grant select (id, email, created_at) on public.credentials to anon;Then ask the database rather than trusting the migration: select has_table_privilege ('anon','public.credentials','select'); -- expect false
select has_column_privilege('anon','public.credentials','password_hash','select'); -- expect false
select has_column_privilege('anon','public.credentials','email','select'); -- expect trueNow the INSERT. Your own evidence points straight at it: raw SQL succeeds, PostgREST fails. A plain Two ways out, depending on what you want back: // return nothing - no SELECT needed
await supabase.from('credentials').insert(row)
// or return only columns anon actually holds SELECT on
await supabase.from('credentials').insert(row).select('id, email')And grant INSERT per column too, for the same reason: revoke insert on public.credentials from anon;
grant insert (email, password_hash) on public.credentials to anon;One more thing worth checking while you are in there, because it fails in the opposite direction from reads: If select grantee, privilege_type
from information_schema.role_table_grants
where table_schema = 'public' and table_name = 'credentials';Two things outside the question, and I would want to be told if it were mine. You posted a production project ref in the opening post. Worth editing out. A table named The catalog checks I use for exactly this class of problem are in one read-only SQL file, if it helps: https://github.com/basildraz-arch/supabase-rls-audit |
Uh oh!
There was an error while loading. Please reload this page.
I'm hitting a PostgREST 42501 "permission denied for table credentials" on INSERT from an anon-key client, and my investigation surfaced what looks like a Postgres/PostgREST privilege interaction I don't fully understand.
Setup:
Table public.credentials with sensitive column password_hash
RLS enabled with permissive policies for INSERT/SELECT/UPDATE
Session 1: REVOKE SELECT ON public.credentials FROM anon then GRANT SELECT (col1, col2, ...) ON public.credentials TO anon — omitting password_hash from the GRANT list. This worked correctly — anon could SELECT allowed columns but got permission-denied on password_hash.
Problem: Now anon-key INSERT (via PostgREST) fails with 42501 permission denied for table credentials. Raw SQL as anon (SET LOCAL ROLE anon; INSERT ...) succeeds. PostgREST fails.
Hypothesized fix: grant table-level SELECT back so PostgREST's INSERT preflight works, then re-REVOKE the specific column. Tested in a rolled-back transaction:
sql
BEGIN;
GRANT SELECT ON public.credentials TO anon;
SET LOCAL ROLE anon;
SELECT password_hash FROM public.credentials LIMIT 1;
-- Expected: permission denied for column password_hash
-- Actual: returned the password_hash value
ROLLBACK;
So the earlier column-level REVOKE is NOT preserved after a subsequent table-level GRANT. Is this expected Postgres behavior? What's the correct pattern for "anon can SELECT most columns but never password_hash, and PostgREST can process INSERTs"?
Supabase project ref: mqlvjwycqoxddooqzxbf (production)
All reactions