Segurança

O que é Row Level Security

Row Level Security (RLS) é um recurso do PostgreSQL que define, linha por linha, quem pode ler e alterar cada registro de uma tabela. Você ativa com ALTER TABLE ... ENABLE ROW LEVEL SECURITY e cria políticas com CREATE POLICY, por exemplo permitindo que cada usuário veja só as linhas em que auth.uid() = user_id. No Supabase, é o que impede qualquer pessoa com a chave pública do projeto de ler o banco inteiro.

Atualizado em · Por Luis Henrique Cuba

Por que o RLS importa em app feito com IA

No Supabase, o aplicativo no navegador fala direto com o banco por uma API gerada automaticamente. Para isso ele usa a chave publicável (sb_publishable_..., ou a antiga anon), que fica visível para qualquer visitante. Isso é previsto: a chave foi feita para ser pública.

O que protege os dados é o RLS. A documentação do Supabase é direta: uma tabela em um esquema exposto sem RLS pode ser lida e alterada por qualquer papel que tenha permissão sobre ela. O erro clássico de app gerado por IA é criar as tabelas por SQL, o app funcionar na demonstração e ninguém ativar o RLS. Quem abrir as ferramentas do desenvolvedor copia a chave e baixa a tabela de clientes.

Como o RLS funciona no PostgreSQL

  1. Negação por padrão

    Com o RLS ativado e nenhuma política criada, o PostgreSQL aplica uma política de negação: nenhuma linha fica visível nem pode ser alterada.

  2. USING

    Filtra as linhas existentes que o comando pode ver, atualizar ou apagar.

  3. WITH CHECK

    Valida as linhas novas ou alteradas em INSERT e UPDATE. Sem ele, o usuário poderia gravar um registro em nome de outro.

  4. Várias políticas

    Políticas permissivas (o padrão) se somam com OU; políticas restritivas (AS RESTRICTIVE) se combinam com E.

  5. Quem ignora o RLS

    Superusuários e papéis com BYPASSRLS sempre ignoram. O dono da tabela também, a menos que você use FORCE ROW LEVEL SECURITY. No Supabase, a chave secreta (service_role) ignora todas as políticas.

Exemplo: cada usuário vê só os próprios pedidos

-- 1. Ative o RLS
ALTER TABLE public.pedidos ENABLE ROW LEVEL SECURITY;

-- 2. Leitura: só as linhas do próprio usuário
CREATE POLICY "pedidos_select_proprios"
ON public.pedidos FOR SELECT
TO authenticated
USING ( (SELECT auth.uid()) = user_id );

-- 3. Inclusão: só em nome de si mesmo
CREATE POLICY "pedidos_insert_proprios"
ON public.pedidos FOR INSERT
TO authenticated
WITH CHECK ( (SELECT auth.uid()) = user_id );

-- 4. Alteração: só as próprias linhas, sem trocar o dono
CREATE POLICY "pedidos_update_proprios"
ON public.pedidos FOR UPDATE
TO authenticated
USING ( (SELECT auth.uid()) = user_id )
WITH CHECK ( (SELECT auth.uid()) = user_id );

-- 5. Índice na coluna usada pelas políticas
CREATE INDEX pedidos_user_id_idx ON public.pedidos USING btree (user_id);
auth.uid() é uma função do Supabase que devolve o id do usuário logado. Em PostgreSQL puro, use current_user ou uma configuração de sessão.

Três detalhes da documentação do Supabase

  1. 01

    Sempre diga para quem é a política

    Use TO authenticated (ou TO anon, se for de propósito). Sem o TO, a política vale para todos os papéis.

  2. 02

    Envolva auth.uid() em SELECT

    Escrever (SELECT auth.uid()) permite ao Postgres calcular o valor uma vez por consulta, em vez de uma vez por linha. Em tabelas grandes, a diferença aparece.

  3. 03

    Indexe as colunas filtradas

    A política vira um filtro em toda consulta. Sem índice em user_id, cada leitura percorre a tabela inteira.

Teste em dois minutos se a tabela está aberta

Pegue a URL do projeto e a chave publicável (as mesmas que estão no código do site) e faça a consulta que um curioso faria, sem estar logado. Use um projeto e uma tabela seus.

  • Abra o Security Advisor do projeto no painel do Supabase e trate primeiro o alerta rls_disabled_in_public (tabela pública sem RLS).
  • Repita o teste logado como o usuário A tentando ler os registros do usuário B.
  • Procure no código do front-end por service_role ou sb_secret_. Se aparecer, troque a chave imediatamente.
curl 'https://SEU_PROJETO.supabase.co/rest/v1/pedidos?select=*' \
  -H "apikey: SUA_CHAVE_PUBLICAVEL"
Se voltarem pedidos de clientes, a tabela está exposta. Com RLS e políticas só para authenticated, a resposta deve ser uma lista vazia.

Ativei o RLS e a consulta voltou vazia

É o comportamento esperado. Ativar o RLS sem criar políticas bloqueia tudo pela API, e o Security Advisor avisa com rls_enabled_no_policy. Crie as políticas de SELECT, INSERT, UPDATE e DELETE que o app precisa.

Resista à tentação de resolver com USING (true). Isso abre a tabela para todo mundo e desfaz a proteção. Se o app precisa que visitantes leiam algo, como um catálogo de produtos, crie uma política só de leitura e só para essa tabela, limitada às linhas que podem mesmo aparecer.

-- Catálogo: visitantes e usuários logados leem só produtos ativos
ALTER TABLE public.produtos ENABLE ROW LEVEL SECURITY;

CREATE POLICY "produtos_leitura_publica"
ON public.produtos FOR SELECT
TO anon, authenticated
USING ( ativo = true );
Sem políticas de INSERT, UPDATE ou DELETE, ninguém altera o catálogo pela API pública. A edição fica com o painel ou com o servidor.
Perguntas frequentes

O que é Row Level Security: dúvidas comuns

A chave anon ou publicável pode ficar no front-end?

Pode, foi feita para isso. Ela só é segura quando todas as tabelas expostas têm RLS e políticas corretas. A chave secreta (service_role ou sb_secret_) nunca pode ir para o navegador.

Minhas funções no servidor param de funcionar com RLS?

Se usam a chave secreta sem token de usuário, não: essa chave ignora as políticas. Por isso mesmo, valide no código do servidor quem pode fazer o quê.

RLS deixa o banco mais lento?

A política vira um filtro em cada consulta. Com índice nas colunas usadas e auth.uid() envolvido em SELECT, o custo costuma ser pequeno. Sem índice, tabelas grandes sentem.

Preciso de RLS se o app não tem login?

Sim, se a tabela está no esquema exposto pela API. Sem login, todo acesso vem como anon; decida tabela por tabela o que esse papel pode ler e nunca libere escrita sem necessidade.

RLS substitui a validação no servidor?

Não. O RLS decide quais linhas cada usuário alcança. Regras de negócio, como limite de desconto ou status permitido de um pedido, continuam precisando de validação no servidor, em funções do banco ou em restrições (CHECK) nas tabelas.

RLS existe fora do Supabase?

Sim. É um recurso nativo do PostgreSQL. O Supabase apenas acrescenta funções como auth.uid() e um painel para criar e revisar as políticas.

Precisa de ajuda para resolver isso na sua empresa?

Respondemos em até 24 horas com as próximas perguntas e uma proposta objetiva.