Saltar para o conteúdo

Como ativar o RLS no Supabase e o que devolve

Ver como MarkdownAbrir em ClaudeAbrir em ChatGPTAbrir em Perplexity

A pergunta também é copiada. Se o chat abrir vazio, cole-a.

Em resumo

Ative o RLS no Supabase com uma instrução por tabela, alter table public.todos enable row level security;, ou com a opção “Enable RLS” do painel. As tabelas criadas no editor SQL ou por uma migração não o trazem ativado por defeito. Logo depois de o ativar, a tabela fica calada em vez de dar erros. No meu teste local, a chave pública e dois utilizadores com sessão iniciada receberam 200 [] em select, update e delete, e só o insert falhou com 42501. A chave secreta continuava a ver as 3 linhas. Uma vista simples sobre a tabela continuava a entregar as 3 linhas à chave pública. Desde 30 de outubro de 2026, uma tabela nova precisa ainda de um grant para que algo lhe chegue, por isso ative o RLS e escreva as políticas na mesma migração que o grant.

Mantenho um verificador de segurança do Supabase gratuito, e “RLS disabled” é o achado que vejo pior explicado. A maioria dos guias diz que, depois de ativar o RLS, “a tabela fica inacessível”. Isso é verdade para as leituras e enganador para as escritas. Por isso construí uma tabela numa stack local do Supabase e enviei-lhe todo o tipo de pedidos, com a chave pública, com dois utilizadores com sessão iniciada e com a chave secreta, depois de cada passo abaixo.

O que é o RLS no Supabase?

O RLS (row-level security, segurança ao nível da linha) é uma funcionalidade do Postgres que filtra linhas por pedido. O Supabase converte cada pedido do supabase-js numa consulta à base de dados que corre como um papel: anon para a chave pública (anon, publishable) e authenticated para um utilizador com sessão iniciada. Com o RLS ativado, o Postgres verifica as suas políticas linha a linha e devolve, altera ou apaga apenas as linhas que uma política permite. Com o RLS desativado, qualquer papel com um grant sobre a tabela chega a todas as linhas.

A chave secreta (service role) é diferente. O seu papel tem BYPASSRLS, por isso o RLS nunca se lhe aplica. É por isso que só deve estar num servidor. A diferença entre as duas chaves está em Supabase anon key vs service role key: o que cada uma abre.

Como se ativa o RLS no Supabase?

De três maneiras, conforme a forma como a tabela foi criada:

  • Ao criar uma tabela no Table Editor, deixe marcada a opção “Enable Row Level Security (RLS)”. No código-fonte do painel do Supabase aparece como “Recommended”, e ao desmarcá-la surge um aviso de que a tabela será legível e alterável por qualquer pessoa.
  • Para uma tabela existente, o guia de segurança da API do Supabase indica: vá a Database, depois a Policies, e selecione “Enable RLS” na tabela.
  • Em SQL, no editor SQL ou numa migração:
alter table public.todos enable row level security;

O guia do Supabase é explícito: as tabelas criadas no painel trazem o RLS ativado por defeito, e as criadas no editor SQL ou por outra ferramenta não. Se constrói o seu esquema com migrações ou com uma ferramenta de IA, verifique cada tabela.

O que acontece logo depois de ativar o RLS?

Nada dá erro, exceto o insert. Criei public.todos com uma coluna de proprietário (user_id uuid default auth.uid()), concedi-a aos papéis da API como nos projetos mais antigos e adicionei três linhas: duas do utilizador A e uma do utilizador B. Depois ativei o RLS sem nenhuma política:

Pedido Chave pública Utilizador A Utilizador B Chave secreta
Select 200, 0 linhas 200, 0 linhas 200, 0 linhas 200, 3 linhas
Insert de linha própria 401, erro 42501 403, erro 42501 403, erro 42501 201, linha inserida
Update de uma linha 200, 0 linhas 200, 0 linhas 200, 0 linhas 200, 1 linha
Delete de uma linha 200, 0 linhas 200, 0 linhas 200, 0 linhas 200, 1 linha

O erro do insert diz new row violates row-level security policy. Update e delete respondem 200 com um array vazio, por isso uma app que não verifica as linhas devolvidas informa “guardado” quando nada foi guardado. Leia as suas escritas depois de ativar o RLS, ou peça uma contagem com Prefer: count=exact.

Antes do RLS, com os mesmos grants, a chave pública podia ler as 3 linhas, inserir, e alterar ou apagar qualquer linha. O utilizador A podia editar e apagar a linha do utilizador B e passar-lhe as suas próprias linhas.

Como voltar a tornar a tabela utilizável?

Escreva uma política por operação. O conjunto habitual para linhas próprias:

create policy "own select" on public.todos for select to authenticated
  using ((select auth.uid()) = user_id);
create policy "own insert" on public.todos for insert to authenticated
  with check ((select auth.uid()) = user_id);
create policy "own update" on public.todos for update to authenticated
  using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "own delete" on public.todos for delete to authenticated
  using ((select auth.uid()) = user_id);

Com elas, o utilizador A via as suas 2 linhas e o utilizador B a sua 1. A chave pública continuava a ver 0. Quando A tentou inserir uma linha de B, ou passar uma linha sua para B, a resposta foi 403, 42501. Essa recusa vem do with check no update. Quando A tentou alterar ou apagar a linha de B, a resposta foi 200, 0 linhas. Há mais padrões (perfis públicos, equipas, MFA) em Exemplos de RLS policy no Supabase, testados pela API.

Um pormenor da chave secreta: uma linha inserida por ela ficou com user_id = null, porque auth.uid() não tem nenhum utilizador por trás dessa chave. O RLS foi ignorado, mas o valor por defeito não fez o que o nome da coluna sugere.

O que leu a chave pública em cada passo?

Linhas lidas pela chave pública, de 3. Uma tabela do Supabase com 3 linhas, após cada passo. Tabela nova, sem grant, RLS off (erro 42501): 0; Com grant, RLS off: 3; RLS on, sem política: 0; RLS on, políticas de linhas próprias: 0; Vista simples sobre a tabela: 3; Vista com security_invoker: 0.
Figura 1. Stack local do Supabase (CLI 2.109.1, Postgres 17.6), 28 de outubro de 2026.

Dois passos deixam passar tudo: um grant sem RLS, e uma vista simples sobre uma tabela com políticas corretas. As duas secções seguintes explicam ambos.

Ainda é preciso RLS depois da mudança de grants de 30 de outubro?

Sim. Desde 30 de outubro de 2026, o Supabase deixou de dar grants automáticos aos papéis da API nas tabelas novas de public, tanto em projetos novos como em existentes. A minha stack local já se comporta assim. Uma tabela acabada de criar com o RLS desativado respondeu 42501 a todos os papéis, incluindo a chave secreta, por isso nada vazou. Os pormenores estão em Supabase, 30 de outubro: o que falha com os novos grants.

Mas o grant volta assim que a app precisa da tabela, e a partir daí o RLS é a única coisa entre a chave pública e todas as linhas. Por isso ponha tudo numa só migração, por esta ordem:

create table public.todos (...);
alter table public.todos enable row level security;
create policy "own select" on public.todos ...;
grant select, insert, update, delete on table public.todos to authenticated;

Conceda apenas aos papéis que precisam da tabela. Quando revoguei tudo a anon, o select da chave pública passou de 200 [] a 401, 42501. O grant é o primeiro cadeado; o RLS, o segundo.

Porque é que uma vista continua a deixar vazar linhas?

Porque uma vista corre com os direitos do seu proprietário, e no Supabase esse é o postgres, que ignora o RLS. Com as políticas de linhas próprias já em todos, um simples create view public.todos_view as select * from public.todos devolveu as 3 linhas à chave pública e ao utilizador B.

Recriada com with (security_invoker = true), a mesma vista devolveu 0 linhas à chave pública, 2 a A e 1 a B, exatamente como a tabela:

create view public.todos_view with (security_invoker = true) as
  select * from public.todos;

O Advisor do Supabase assinala a variante simples como security_definer_view; o que significa e como corrigir está em a nota sobre security definer view no Supabase.

Como ativar o RLS automaticamente em cada tabela nova?

Com um event trigger. O guia de event triggers do Supabase fornece uma função rls_auto_enable() e um trigger ensure_rls que corre depois de create table, create table as e select into em public. Instalei-o tal como está publicado e obtive:

  • Uma tabela criada depois com create table tinha o RLS ativado sem nenhum alter. A chave pública recebeu 200 [] no select e 401 no insert.
  • Uma tabela criada com create table as select também tinha o RLS ativado.
  • Uma tabela criada antes do trigger continuou desativada. O trigger não é retroativo.

Sem o trigger, uma segunda tabela criada ao lado da protegida tinha o RLS desativado, e depois de concedida, a chave pública lia-a e escrevia-lhe livremente. Cobre também apenas public; os outros esquemas expostos precisam da sua própria linha na função.

O FORCE ROW LEVEL SECURITY ajuda?

Não para os papéis com que se preocuparia. alter table ... force row level security faz o RLS aplicar-se também ao proprietário da tabela. No Supabase, tanto o postgres como o service_role têm BYPASSRLS, por isso depois de force o proprietário continuava a contar 3 linhas, a chave secreta continuava a ver tudo, e uma vista propriedade do postgres continuava a vazar. Só mudou alguma coisa para um papel proprietário que criei sem BYPASSRLS: 2 linhas antes, 0 depois.

Como verificar o seu próprio projeto?

  • No editor SQL, liste as tabelas de public com o RLS desativado:
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind in ('r', 'p')
  and not c.relrowsecurity
order by 1;
  • O Advisor do painel reporta o mesmo como rls_disabled_in_public; o que esse erro significa está em a nota sobre RLS disabled in public.
  • O verificador de segurança do Supabase gratuito lê o seu projeto em modo só de leitura e lista as tabelas com o RLS desativado a que a API chega, as vistas que contornam o RLS e as políticas que permitem o acesso a todos, com o SQL de correção para cada uma.
  • Um scanner não pode saber que linhas pertencem a quem. Para o provar, envie pedidos como dois utilizadores, como acima. O método completo para uma app com papéis está em Testar Supabase RLS.

Como testei

  • Supabase CLI 2.109.1 num portátil: Postgres 17.6, PostgREST 14.14, Auth 2.192.0. Dois utilizadores com sessão iniciada por palavra-passe, mais as chaves locais publishable e secreta.
  • Uma tabela, public.todos, reposta nas mesmas 3 linhas antes de cada pedido. Cada passo foi aplicado como SQL, e cada pedido passou pela API REST, registando-se o código de estado e o corpo.
  • Os grants dos passos 2 a 5 foram adicionados à mão, para igualar um projeto criado antes de 30 de outubro de 2026.

Limites. Uma stack local, dados inventados, 28 de outubro de 2026. Os passos do painel são citados da documentação do Supabase, não percorridos com cliques. As tabelas criadas pelo papel próprio do painel podem receber outros grants por defeito. Não testei isso, nem Storage, Realtime ou GraphQL.

Não tem a certeza de que as suas tabelas estão fechadas?

Falo com gosto sobre isso e vejo um esquema consigo. Sem pitch.

Fale comigo

© 2026 Denys Zaichenko. Artigo original: https://madsalt.tech/pt/blog/how-to-enable-rls-in-supabase/