Skip to content

23 tools · 104 fixes · 29 issue guides · Sources: GitHub, Supabase, OWASP, Vercel + 28

Supabase

New Supabase tables need an explicit grant in Supabase

By ShipSecurely Editorial · Updated · How we make these guides

high

Put the grant, row level security, and the policies in the same migration. Grant only the commands each role needs.

Fix for Supabase

Put the grant, row level security, and the policies in the same migration. Grant only the commands each role needs. The changelog's own example grants select to anon, and select, insert, update, delete to authenticated and service_role, then enables row level security and adds a policy. Match the grant to the role that should have access. A table only signed-in users should read does not need a grant to anon.

-- The role that inserts from the browser, under row level security.
grant select, insert, update, delete on public.notes to authenticated;

-- service_role bypasses row level security. Server code only. Never the browser.
grant select, insert, update, delete on public.notes to service_role;

alter table public.notes enable row level security;

-- This sample assumes a user_id column. Use the real owner column.
create policy "notes_select_own"
on public.notes
for select
to authenticated
using (auth.uid() = user_id);

PostgreSQL's GRANT reference says a table grant does not extend to sequences the table uses, including a serial column. Grant usage and select on that sequence to the role that actually inserts: authenticated when a signed-in user inserts under row level security, or service_role when only the server inserts. The block below does nothing when notes.id has no sequence, so a missing sequence does not make the script fail. pg_get_serial_sequence finds the sequence behind a serial column and behind an identity column. Change authenticated in the format string if the inserting role is service_role.

do $$
declare
  seq regclass := pg_get_serial_sequence('public.notes', 'id');
begin
  if seq is not null then
    execute format('grant usage, select on sequence %s to authenticated', seq);
  end if;
end $$;

Add insert, update, and delete policies the same way when those commands are granted. An insert policy needs with check. Repeat the signed-in request and confirm the 42501 is gone, and that a stranger's row still does not come back.

Do not "fix" the error with grant all on public.notes to anon while row level security is off. That publishes every row to anyone who has the anon key, which is the bug in Supabase tables without row level security. A grant without row level security is how a new table becomes world-readable. A policy without a grant is why a secured table still returns 42501.

alter default privileges changes future objects only. The changelog's opt-in SQL revokes future table grants (select, insert, update, delete) and future sequence grants (usage, select) from anon, authenticated, and service_role. It does not remove grants from tables that already exist. Securing your API also revokes default execute on functions in public from those roles, and execute from public. A new function is not on the Data API until you grant execute to the role that should call it.

The five-minute check, the other platforms, and the agent prompt are on the full guide. More Supabase fixes are on the Supabase page.