FixVibe

// docs / baas security / supabase rls scanner

Scanner de RLS Supabase: encontre tabelas com segurança em nível de linha ausente ou quebrada

A segurança em nível de linha (RLS) é a única coisa entre os dados dos seus clientes e a internet quando você publica uma aplicação apoiada por Supabase. Ferramentas de codificação com IA geram código em formato de RLS que compila, é publicado e vaza dados silenciosamente — tabelas criadas sem RLS habilitado, políticas que leem mas nunca restringem, predicados que comparam uma coluna consigo mesma. Este artigo mostra o que um scanner de RLS Supabase pode provar de fora, as quatro formas de RLS quebrada que aparecem em apps gerados por IA e como varrer seu próprio deploy em menos de um minuto.

O que uma varredura RLS externa pode provar

A passive RLS scan looks at your Supabase project the way an anonymous visitor would. It uses only the publishable anon key — the same key your browser uses — and never authenticates as a user or touches service-role privileges. Anything it can see, an unauthenticated attacker on the internet can see.

De fora do banco de dados, um scanner pode confirmar o seguinte com alta confiança:

  • RLS está desabilitado em uma tabela. O PostgREST retorna linhas para um SELECT anônimo quando RLS está desligado ou quando uma política permite. Qualquer caso é um achado.
  • O role anônimo pode listar tabelas. Um GET /rest/v1/ com a chave anon retorna o schema OpenAPI para toda tabela em que o role anon tenha qualquer privilégio. Apps gerados por IA frequentemente concedem USAGE no schema e SELECT em cada tabela, o que expõe o mapa completo do schema mesmo quando RLS nega as leituras reais.
  • Read access does not establish write safety. A passive scan cannot prove that INSERT, UPDATE, or DELETE policies protect every user and tenant. Review write policies and test them with isolated fixtures under the intended roles.
  • A chave de role de serviço está no bundle do navegador. Adjacente ao RLS: se um scanner encontrar SUPABASE_SERVICE_ROLE_KEY ou qualquer JWT com role: service_role no bundle JavaScript, o RLS é irrelevante — quem possui essa chave contorna qualquer política.

O que uma varredura externa não pode provar

Seja honesto sobre os limites do scanner. Uma varredura RLS externa não pode ler sua tabela pg_policies, seus arquivos de migração nem o predicado exato de nenhuma política. Ela infere de comportamento de caixa-preta, o que significa que às vezes vai reportar um achado que acaba sendo dado público intencional (tabela de newsletter de marketing, catálogo público de produtos). O relatório do FixVibe marca esses como confiança média quando o scanner não consegue desambiguar a intenção — revise o nome da tabela e decida.

As quatro formas de RLS quebrada que ferramentas de IA produzem

When you point Cursor, Claude Code, Lovable, or Bolt at Supabase, the same four broken-RLS patterns come up again and again. Each one passes type-check, compiles, and ships:

Forma 1: RLS nunca habilitado

The most common failure mode. The migration creates the table but the developer (or the AI tool) forgets ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST happily serves the entire table to anyone with the anon key. Fix: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;, then add a policy per command. Enabling RLS with no policies denies all access through the API; the policies decide who gets in.

sql
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;

Forma 2: RLS habilitado, sem políticas

Uma falha mais sutil. RLS está habilitado mas nenhuma política foi escrita. O padrão no PostgreSQL é negar, então usuários autenticados não veem nada — e o desenvolvedor adiciona USING (true) para o app funcionar, o que permite que todos leiam tudo. Correção: escreva uma política que restrinja por auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); e uma política INSERT/UPDATE/DELETE correspondente.

sql
CREATE POLICY "select_own"
  ON public.[name]
  FOR SELECT
  USING (auth.uid() = user_id);

Forma 3: política compara coluna consigo mesma

Um artefato de copiar e colar. O desenvolvedor escreve USING (user_id = user_id) — que é sempre verdadeiro — em vez de USING (auth.uid() = user_id). A verificação de tipos passa; a política permite cada linha. Correção: sempre compare uma coluna com uma chamada de função (auth.uid(), auth.jwt()->>'org_id', etc.), nunca consigo mesma ou com uma constante.

Shape 4: Overly permissive write policies

With RLS enabled, a role with no applicable policy is denied by default. A SELECT policy does not itself allow INSERT. Write exposure can arise from a permissive applicable INSERT or ALL policy, disabled RLS, or a role that bypasses RLS. Fix: inspect table privileges and every applicable policy, use ownership-scoped WITH CHECK conditions, and test writes with separate user identities.

Como funciona o scanner de RLS Supabase do FixVibe

FixVibe's Supabase check works from the deployed app, in three steps:

  1. Find the project. FixVibe loads the deployed app and reads the Supabase project URL and public key from the same configuration your browser receives. No guessing, no brute force.
  2. See what is exposed. FixVibe checks which tables the anonymous role can see, without reading row data at this step.
  3. Stage 3 — inspect anonymous read access. The passive check reports observable access to discovered resources. It does not create, update, or delete database rows, and it cannot establish that all write policies or cross-user boundaries are correct.

Each finding ships with the exact request URL, response status, response shape (header-only), and the table name. FixVibe now labels whether the fix belongs in code/config, a provider console, DNS, secret rotation, or manual review so the next step matches who can actually apply it.

O que fazer quando o scanner encontra algo

Cada achado de RLS é uma emergência em produção. Endpoints PostgREST públicos são varridos por atacantes em minutos. A sequência de remediação é mecânica:

  1. Audite cada tabela. Execute SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public'; no editor SQL do Supabase. Qualquer linha com rowsecurity = false é problema.
  2. Enable RLS on every public table. Default to ENABLE ROW LEVEL SECURITY on every table created — make it a migration template.
  3. Escreva políticas comando por comando. Não use FOR ALL USING (true). Escreva políticas explícitas para SELECT, INSERT, UPDATE, DELETE — cada uma restrita a auth.uid() ou a uma coluna de org-id de auth.jwt().
  4. Verifique com uma segunda conta. Cadastre-se como outro usuário, tente ler os registros de outro usuário via API REST diretamente. Se a resposta for 200, a política está quebrada.
  5. Re-scan. After applying the fix, re-run a FixVibe scan against the same URL. The Supabase RLS finding should clear.
sql
-- Audit every table for missing RLS. Run in the Supabase SQL editor.
SELECT schemaname, tablename, rowsecurity
FROM   pg_tables
WHERE  schemaname = 'public'
ORDER  BY rowsecurity, tablename;

Como isso se compara a outros scanners

A maioria das ferramentas DAST genéricas (Burp Suite, OWASP ZAP, Nessus) não sabe o que é PostgREST. Elas vão percorrer seu app, ignorar o caminho /rest/v1/ e reportar as páginas HTML que entendem. Snyk e Semgrep são ferramentas de análise estática — encontram arquivos de migração no seu repositório com chamadas RLS ausentes, mas não conseguem provar que o banco publicado está mal configurado. O FixVibe fica nessa lacuna: passivo, ciente de BaaS, focado no que um atacante não autenticado consegue provar a partir da URL pública.

Perguntas frequentes

O scanner vai ler ou modificar meus dados?

The passive RLS check is read-only. It examines observable anonymous read access and reports limited evidence. It does not perform database write operations or prove that every user and tenant is correctly isolated.

Isso funciona se meu projeto Supabase estiver pausado ou em domínio personalizado?

Projetos pausados retornam 503 em cada requisição — o scanner reporta o projeto como inalcançável. Domínios personalizados funcionam desde que o app publicado ainda carregue o SDK cliente Supabase no navegador; o scanner extrai a URL do projeto do bundle de qualquer forma.

E se minha chave anon for rotacionada ou minha chave publicável mudar?

Rode a varredura de novo. O scanner re-extrai a chave do bundle atual a cada execução. A rotação invalida apenas o relatório anterior, não o estado das políticas do banco.

O scanner verifica o novo modelo de chave publicável Supabase (sb_publishable_*)?

Sim. O detector reconhece tanto os JWTs anon antigos quanto as chaves mais novas sb_publishable_* e as trata identicamente — ambas são feitas para serem públicas e ambas deixam RLS como única linha de defesa.

Próximos passos

Run a free FixVibe scan against your production URL — the Supabase RLS check runs on every plan including the free tier. For a deeper read on what else can leak from a Supabase project, see Supabase service role key exposed in JavaScript and Supabase storage bucket security checklist. For the umbrella view across all BaaS providers, read BaaS misconfiguration scanner.

// varra sua superfície baas

Encontre a tabela aberta antes que outra pessoa o faça.

Coloque uma URL de produção. O FixVibe enumera os provedores BaaS com que seu app conversa, identifica seus endpoints públicos e relata o que um cliente não autenticado pode ler ou escrever. Grátis, sem instalação, sem cartão.

  • Plano gratuito — 3 varreduras/mês, sem cartão de cadastro.
  • Identificação BaaS passiva — sem necessidade de verificação de domínio.
  • Supabase, Firebase, Clerk, Auth0, Appwrite e mais.
  • Coding-agent prompts where code/config applies, plus provider-console steps for hosted BaaS fixes.
Rodar varredura BaaS gratuita →

sem necessidade de cadastro

Scanner de RLS Supabase: encontre tabelas com segurança em nível de linha ausente ou quebrada · FixVibe