Início / RLS / Upsert: new row violates RLS · Read in English
Supabase RLS · Correção de erro
Upsert falha: new row violates row-level security policy
O upsert() vira insert ... on conflict do update. Pra isso, o Postgres confere as policies de UPDATE e SELECT além da de INSERT, e faz isso mesmo quando a linha é nova. Crie uma policy de UPDATE (com a mesma condição de dono) e uma de SELECT, e dê grant de update.
01 Por que o insert funciona e o upsert não
Por padrão, supabase.from('profiles').upsert(row) é enviado como insert ... on conflict (id) do update. Um comando que pode atualizar precisa de todas as policies que talvez use: INSERT pra linha nova, UPDATE pra linha existente e SELECT pra ler a linha com que conflita. Se a de UPDATE ou a de SELECT faltar, o Postgres recusa o comando logo de cara com new row violates row-level security policy, mesmo quando a linha ainda não existe.
02 A correção
migration.sql
create policy "owner inserts" on profiles for insert to authenticated with check (id = (select auth.uid())); create policy "owner updates" on profiles for update to authenticated using (id = (select auth.uid())); create policy "owner reads" on profiles for select to authenticated using (id = (select auth.uid())); grant select, insert, update on profiles to authenticated;
Mantenha as três com a mesma condição de dono. O usuário continua sem conseguir fazer upsert com o id de outra pessoa: a checagem do INSERT recusa.
03 Se você só insere
Se a linha nunca deve ser sobrescrita, não use upsert(). Use insert() e trate o erro de chave duplicada (23505), ou passe { ignoreDuplicates: true } pra requisição virar on conflict do nothing.
04 Prove
Seu app está vazando agora?
Cole a URL do seu app no ar. O scanner lê a página e o JavaScript atrás de uma chave service_role do Supabase, o vazamento que passa por cima de todas as policies deste guia.
Pegue isso antes de ir pro ar
airlock-rls é um gate de CI que quebra o build quando uma tabela vai pro ar exposta ou uma policy é permissiva: o mesmo tipo de bug, pego no CI contra um banco com as suas migrations aplicadas, antes de chegar em produção.
Ou comece pelo nextjs-supabase-starter: auth + uma tabela com RLS + um teste de isolamento, pra começar de um padrão que já vem selado.
Baixe o cheat sheet de RLS do Supabase
As regras de ouro, as armadilhas que vazam em produção, policies corretas e o teste de isolamento, numa página só (em inglês).
FAQ
Por que o Postgres precisa de SELECT num upsert?
Pra detectar o conflito ele precisa olhar a linha existente, e essa leitura passa pelas suas policies de SELECT. Sem nenhuma, o upsert não pode ser checado e é recusado.
Isso vale também pro upload no Storage com upsert: true?
Vale. Substituir um arquivo é um upsert em storage.objects, então ele precisa de policies de SELECT e UPDATE além da de INSERT.