Noções de segurança
"Row-level security policy for table objects" ao enviar
"New row violates row-level security policy for table objects" quer dizer que o seu envio não tem regra insert. Deixar o bucket público não cria nenhuma.

Em resumo
- "New row violates row-level security policy for table objects" quer dizer que o seu envio chegou ao Supabase Storage e nenhuma regra em storage.objects deixou o arquivo entrar. O arquivo não foi guardado, e o resto do bucket continua intacto.
- O Storage guarda as regras numa única tabela para todos os buckets do seu projeto, e é por isso que a mensagem cita uma tabela que você nunca criou.
- Deixar o bucket público não resolve. O Supabase confere os envios de qualquer jeito, e público só decide quem pode abrir um arquivo cujo endereço já tem.
- Um envio precisa de uma regra insert em storage.objects. Se o seu código salva com upsert, também precisa de select e update.
O seu app envia um arquivo. Funcionava no preview do seu builder, ou funcionava semana passada, e agora toda tentativa volta com uma frase sobre row-level security:
new row violates row-level security policy for table "objects"
Quase toda essa frase vai parecer conhecida se você já passou por isso numa
tabela sua. O que muda é a última palavra, porque objects não é uma tabela que
você criou.
Esta é a parte que guia após guia erra: a primeira solução que você vai achar é deixar o bucket público, e ela não faz nada pelos envios. A própria documentação do Supabase diz isso em uma linha. Essa chave deixa o envio recusado e abre os arquivos que já estavam lá dentro.
O que significa "new row violates row-level security policy for table objects"
O seu envio chegou ao Supabase Storage, o Storage perguntou às regras postas
numa tabela chamada storage.objects se aquele arquivo podia entrar, não achou
nenhuma que dissesse sim, e recusou.
A linha da mensagem existe de verdade. O Storage guarda uma linha em
storage.objects para cada arquivo que tem, com o nome do arquivo, o bucket em
que ele está e quem o colocou ali. Escrever um arquivo é escrever essa linha,
então as regras dessa tabela decidem se o envio acontece. Nada se perdeu: nenhum
arquivo foi guardado, e o resto do bucket está exatamente como há um minuto.
A redação vem do Postgres, o banco de dados que roda embaixo do Supabase, e por
isso soa a maquinário. Ela traz o código 42501, o mesmo de
um salvamento recusado numa das suas próprias tabelas.
Por que a mensagem cita "objects" e não o seu bucket
Porque o Storage guarda as regras de todos os buckets do seu projeto nessa única tabela.
Um bucket organiza arquivos. Ele não tem regras próprias, e não existe nenhum
editor de policies pendurado nele. storage.objects é onde as regras moram,
para todos os seus buckets ao mesmo tempo, e bucket_id é uma coluna dela.
Então uma regra que diz "só no bucket avatars" é escrita como uma condição sobre
essa coluna.
É também por isso que a regra de tabela que você escreveu semana passada não
ajudou. Uma regra em profiles é uma regra sobre linhas de profiles, e um
arquivo é uma linha de storage.objects.
Preciso deixar o bucket público para conseguir enviar?
Não. Atrás de um bucket existem duas perguntas diferentes, e a chave responde só uma delas. Quem pode tirar um arquivo é o que público decide. Quem pode colocar um é o assunto do seu erro, e a documentação do Supabase é explícita que o ajuste não chega até lá. Da página que descreve os dois tipos de bucket, lida em 11 de outubro de 2026:
O controle de acesso continua sendo aplicado a outros tipos de operação, incluindo enviar, apagar, mover e copiar.
O que público de fato faz está dito com a mesma clareza nessa página: quem tem o endereço de um arquivo consegue abri-lo sem entrar na conta. O ajuste não é mais do que isso.
Então virar a chave deixa você no único estado que ninguém quer. O envio continua recusado, porque enviar nunca foi o que o ajuste controlava, e os arquivos que já estavam no bucket agora podem ser abertos por qualquer um que tenha o endereço deles. O que um bucket público custa de verdade é uma pergunta diferente desse erro, e vale ler antes de mexer na chave.
O que um bucket realmente confere quando você envia
Uma regra, e o nome dela é insert.
A página de controle de acesso do Supabase deixa o padrão claro: sem policies, o
Storage não permite nenhum envio a um bucket, e você libera operações uma a uma
escrevendo-as em storage.objects. Depois ela nomeia a que você precisa:
Por exemplo, a única policy de RLS necessária para enviar objetos é conceder a permissão
INSERTna tabelastorage.objects.
Existe uma segunda metade disso, e é o motivo mais comum de uma regra insert não tirar o erro:
Para permitir sobrescrever arquivos pela funcionalidade
upsert, você precisará conceder também as permissõesSELECTeUPDATE.
Se o seu envio manda upsert: true, que pede ao Storage para substituir um
arquivo de mesmo nome quando já existe um, então uma regra insert sozinha não
basta. Substituir um arquivo são três perguntas em vez de uma: você pode
acrescentar esta linha, pode ver a que está lá, e pode alterá-la.
| O que o seu app pede ao Storage | O que ele precisa em storage.objects |
|---|---|
| enviar um arquivo novo | uma regra insert |
substituir um arquivo de mesmo nome (upsert) | insert, mais select e update |
| abrir um arquivo num bucket privado | uma regra select |
| listar o que há num bucket | uma regra select |
| apagar um arquivo | uma regra delete |
Você não precisa escrever as cinco. Escreva as que o seu app de fato faz, que na maioria dos envios é a primeira linha e às vezes a segunda.
A regra que deixa o seu app enviar, e só o seu app
Nomeie o bucket, e diga para quem a regra vale.
O ponto de partida da documentação limita um envio a um bucket e a visitantes logados:
create policy "Allow authenticated uploads"
on storage.objects
for insert
to authenticated
with check (bucket_id = 'my_bucket_id');
to authenticated é a parte que não convém pular. Ela quer dizer que a regra
vale para visitantes logados; tire-a e a regra passa a valer para todo mundo,
estranhos inclusive.
Para qualquer coisa que pertença a uma pessoa específica, a versão que o Supabase publica põe os arquivos de cada pessoa numa pasta com o nome dela:
create policy "Allow authenticated uploads"
on storage.objects
for insert
to authenticated
with check (
bucket_id = 'my_bucket_id' and
(storage.foldername(name))[1] = (select auth.jwt()->>'sub')
);
storage.foldername(name) divide o caminho de um arquivo guardado nas pastas
dele, então [1] é a primeira. auth.jwt()->>'sub' é o id de quem está logado
e fazendo o pedido. Lidas juntas, as duas linhas dizem que você pode colocar um
arquivo na pasta com o seu nome, naquele bucket, e em nenhum outro lugar.
Os dois são exemplos do Supabase e não nossos, lidos em 11 de outubro de 2026,
com my_bucket_id onde eles deixaram, para você ver qual parte é o nome do seu
próprio bucket.
Se preferir ver o resultado de fora, o nosso escaneamento gratuito pergunta ao seu projeto em produção quais dos seus buckets vão entregar o conteúdo a um estranho. Leva uns 20 segundos e não precisa de conta: escaneie o seu app.
O que uma regra de leitura entrega sem que ninguém peça
Listar um bucket e baixar dele são o mesmo privilégio, então uma regra escrita para fazer os seus downloads funcionarem também deixa o conteúdo do bucket listável.
A página de funções auxiliares do Supabase diz isso direto: um único privilégio
de SQL como SELECT é usado por várias ações do Storage. A página de controle
de acesso depois avisa do caso específico, embaixo de um exemplo que abre um
bucket de avatares para todo mundo:
O filtro
allow_any_operation()é crítico aqui, porque sem ele os usuários conseguiriam listar o conteúdo do bucket.
Essa é a brecha que a gente mede de fora. Escaneamos 8.435 apps no ar que citam um projeto Supabase. A verificação de buckets obteve resposta em 4.703 delas, e 792 dessas responderam ao nosso pedido de listagem com os nomes do que tinham dentro, sem ninguém logado. Isso é cerca de uma em cada seis das apps que conseguimos perguntar.
Muitas vezes um nome já basta, porque um bucket que lista poupa um estranho de
adivinhar nomes de arquivo. invoice-2026-03-hannah.pdf diz de quem é o arquivo
antes de alguém abrir.
A correção que o Supabase documenta é dizer para qual ação do Storage a regra vale:
create policy "Allow users to list their own objects"
on storage.objects
for select
to authenticated
using (
storage.allow_only_operation('object.list')
and owner_id = (select auth.uid()::text)
);
storage.allow_only_operation e a irmã storage.allow_any_operation são o jeito
de uma regra select se estreitar a uma das ações que dividem o privilégio. Sem
uma das duas, uma regra que você escreveu para o seu app poder mostrar uma
imagem é também uma regra que vai ler em voz alta tudo o que há no bucket.
Se um bucket listável é um problema para o seu app depende do que tem dentro, e essa é uma pergunta que o resultado do escaneamento não responde por você.
Quando público é a resposta certa
Quando os arquivos existem para serem vistos por qualquer um que peça, o que é uma categoria real e da qual o Supabase dá os próprios exemplos.
Os casos de uso que o Supabase cita para um bucket público são fotos de perfil, mídia pública e conteúdo de posts de blog. São arquivos que o seu app mostra a um visitante que não entrou na conta, e servi-los de um bucket público ainda é mais rápido, porque os dois tipos de bucket são cacheados de formas diferentes.
Para o outro tipo, a documentação cita dois jeitos de tirar um arquivo de um bucket privado: um download carregando o token da pessoa logada, ou um link assinado que funciona por um tempo limitado. Os dois deixam a decisão com as suas regras em vez de deixar com quem achou o endereço.
Como manter o bucket certo depois de hoje
Com as regras corretas, duas coisas ainda se mexem: alguém vira a chave enquanto persegue um bug sem relação, e um arquivo some.
O Reeve Monitor roda as nove verificações de fora de novo por você:
- as nove verificações a cada hora, em até três apps, incluindo a de listagem de bucket
- se o app responde, a cada 60 segundos
- uma mensagem quando um resultado muda, para que um bucket que abriu de madrugada não espere você olhar
- um relatório mensal do que foi visto
O Reeve Care guarda uma cópia do que está lá dentro:
- uma cópia criptografada do seu banco Supabase toda noite, guardada onde o seu projeto não alcança
- cada cópia verificada antes de contar, contando as linhas de cada tabela
- também os seus arquivos enviados, assim que você conectar um acesso de Storage
- uma restauração em um clique quando precisar
- tudo o que o Monitor faz
Os dois estão na página de preços, que às vezes fica abaixo do valor de tabela daqui e nunca acima.
O que fazer hoje
O que fazer
- Leia a mensagem como uma recusa. O arquivo não foi guardado, o bucket está igual, e não há nada a recuperar.
- Crie uma regra
insertemstorage.objectsque cite o seu bucket embucket_ide carregue a cláusulaTOque você queria. - Se o seu envio manda
upsert: true, acrescenteselecteupdate, senão a regra insert sozinha não tira o erro. - Deixe a chave de público onde está enquanto faz isso. Sobre envios ela não tem nada a dizer, e sobre quem pode abrir o que já está guardado ela tem.
- Leia cada regra
selectemstorage.objectse pergunte o que ela permite além do download para o qual você a escreveu. A listagem divide esse privilégio.
Comece pelo bucket de onde veio o erro, e depois olhe os outros buckets do mesmo projeto, porque uma regra escrita de forma ampla uma vez costuma ter sido colada duas. A checklist de segurança em 10 minutos cobre o que mais costuma ficar aberto num app recém-lançado, e o guia de segurança do Supabase passa pelo resto do que um estranho consegue alcançar.
Perguntas frequentes
Por que o meu upload no Supabase falha com um erro de row-level security?
Porque o Supabase Storage perguntou às regras postas numa tabela chamada storage.objects se o seu arquivo podia entrar, e não achou nenhuma que dissesse sim. O Storage escreve uma linha nessa tabela para cada arquivo que guarda, e o Row Level Security vale para essa linha igual a qualquer outra linha de qualquer outra tabela. Nenhum arquivo foi guardado e nada do que já estava no bucket mudou. A mensagem traz o código do Postgres 42501, o mesmo de quando um salvamento numa tabela sua é recusado.
Preciso deixar o meu bucket público para conseguir enviar?
Não, e deixar público não ajuda. O Supabase documenta que o controle de acesso continua valendo para enviar, apagar, mover e copiar, esteja o bucket como estiver. Público muda uma coisa só: se quem tem o endereço de um arquivo consegue abrir esse arquivo sem entrar na conta. Então a chave deixa o seu envio recusado e faz com que os arquivos que já estavam lá possam ser abertos por qualquer um que tenha o endereço deles.
De quais policies um bucket precisa?
Um envio precisa de uma regra insert em storage.objects. Se o seu código substitui arquivos de mesmo nome, que é o que o upsert faz, o Supabase diz que também precisa de select e update. Abrir um arquivo num bucket privado precisa de uma regra select, e listar o que está no bucket também, porque essas duas ações do Storage rodam sobre o mesmo privilégio de SQL. Apagar precisa de uma regra delete. Não é obrigatório escrever as quatro, só as que o seu app de fato faz.
Um bucket público é perigoso?
Depende inteiramente do que tem dentro. Público é o ajuste certo para fotos de perfil, logos e qualquer outra coisa que o seu app mostre a um visitante que não entrou na conta, que é justamente o que o Supabase lista como casos de uso dele. É o ajuste errado para notas fiscais, exportações ou qualquer coisa que pertença a uma pessoa específica, e para isso você quer um bucket privado com um link assinado. A pergunta nunca foi se público é ruim.
Como eu confiro se o meu bucket pode ser listado?
Pergunte de fora, sem nenhuma conta conectada, exatamente como um estranho faria. A listagem é concedida por uma regra select e não pela chave de público, então nem a chave no painel nem uma olhada na sua lista de policies respondem isso. O nosso escaneamento gratuito faz isso no seu projeto em produção e diz quais dos seus buckets responderam com os nomes do que tinham dentro.