Pular para o conteúdo
← Notas técnicas

Banco de dados

Isolamento multi-tenant com RLS em PostgreSQL — o que quebra em testes e por quê

Papel em tabela separada, função security definer e uma suíte que tenta vazar dado entre tenants de propósito.

Row Level Security move o isolamento para onde ele resiste a erro de aplicação. Um filtro esquecido em uma consulta não vaza dado se a política do banco não deixar. Isso só vale se as políticas forem escritas com cuidado, e há dois erros que aparecem quase sempre.

Erro 1: papel guardado na tabela de perfil

Se o papel do usuário está em uma coluna da tabela que o próprio usuário pode atualizar, o sistema tem escalonamento de privilégio por design. Papel vive em tabela própria, com chave única por usuário e papel, sem política de escrita para o usuário comum.

Erro 2: política que consulta a própria tabela

Uma política que precisa ler outra tabela protegida por RLS entra em recursão ou avalia com o contexto errado. A saída é uma função security definer, estável, com search_path fixo, que faz a checagem fora do alcance da política.

create or replace function public.tem_papel(_user_id uuid, _papel app_role)
returns boolean
language sql
stable
security definer
set search_path = public
as $$
  select exists (
    select 1 from public.user_roles
    where user_id = _user_id and role = _papel
  )
$$;

A suíte que precisa existir

Teste de RLS que só verifica o caminho feliz não prova nada. O teste útil autentica como o tenant A e tenta ler, escrever, atualizar e apagar linha do tenant B, em cada tabela, por cada papel. Ele falha o build quando qualquer uma dessas operações retorna linha ou sucesso.

  • Leitura cruzada direta em cada tabela.
  • Leitura cruzada por junção — o vazamento costuma aparecer aqui, não na consulta simples.
  • Escrita com id de outro tenant no corpo da requisição.
  • Consulta agregada: count e sum vazam existência mesmo sem devolver linha.
  • Acesso por função e por view, que podem carregar security definer sem que ninguém tenha pensado nisso.

Também vale conferir os GRANTs. RLS sem GRANT bloqueia legítimo; GRANT sem RLS libera tudo. Os dois precisam estar na mesma migração, na mesma revisão.

Vamos conversar?

Me conta qual casamento você quer resolver primeiro. Eu respondo, eu mesmo, em até um dia útil.