PostgreSQL · 42501
permission denied for sequence — serial columns and Supabase grants
Luca Urti
permission denied for sequence
Why does my insert fail with “permission denied for sequence”?
The row-level permission is fine and the table's serial primary key is not. A serial column is sugar for an integer with a default of nextval on a separate sequence object, and calling nextval requires its own usage privilege on that sequence — granting insert on the table does not carry it. The result is an insert that passes every policy you wrote and fails on the identifier. It shows up in tables created by a hand-written or generated migration rather than through the dashboard, which grants the sequence alongside the table.
The fix you should prefer is not a grant
generated always as identity is the modern spelling and it owns its sequence internally, so there is no separate object to grant and no separate thing to forget. For anything a client can see, a uuid default is better still: a sequential integer key tells every user how many rows the table has and lets them guess the neighbouring ones, which turns a missing ownership check into a walk through the whole table rather than a single leaked row.
That last point is worth taking seriously in an app where identifiers appear in URLs. Guessable keys do not create the vulnerability, but they decide whether it costs an attacker one request or none.
The fix
grant narrowly now, migrate the column later
-- Immediate: the specific sequence, not every sequence in the schema. grant usage, select on sequence public.leads_id_seq to anon, authenticated; -- Better, for new tables — identity owns its sequence, nothing to grant: -- create table public.leads ( -- id bigint generated always as identity primary key, -- email text -- ); -- Best for anything whose id reaches a client, because a sequential id is a -- map of the table: -- create table public.leads ( -- id uuid primary key default gen_random_uuid(), -- email text -- );
The fix that works and costs you the database
grant usage on all sequences in schema public to anon; clears the error everywhere at once, and combined with alter default privileges it pre-grants every sequence a future migration creates. On its own a sequence grant leaks little — but it is almost never issued on its own. It travels with the blanket table grant that goes with it, and that pair is how a table added next month arrives on the public API before anybody has written a policy for it.
Whether the trap is already in your repo is a question you can answer
Sentris reads the SQL and the client code, so it reports the shortcut above where it was actually taken — a service_role key in a browser bundle, a policy that is using (true), a table with RLS switched off. A scan needs no account and no card. How often it is wrong is measured and published.