FixVibe

// docs / baas security / supabase storage

Checklist de segurança de buckets Supabase Storage: 22 itens

O Supabase Storage é uma camada fina sobre um bucket compatível com S3 mais o mesmo modelo de segurança em nível de linha que o banco. Isso significa que as mesmas armadilhas de RLS que afetam tabelas afetam o acesso a arquivos — e algumas específicas de storage que aparecem quando ferramentas de codificação com IA fazem o cabeamento de uploads. Este checklist tem 22 itens em cinco seções: configuração de bucket, políticas RLS, validação de upload, URLs assinadas e higiene operacional. Cada um é verificável em menos de 15 minutos.

Cada item abaixo é essencial. Para os mecanismos RLS subjacentes, veja Scanner de RLS Supabase. Para a classe de exposição de chaves adjacente ao storage, veja Chave de role de serviço Supabase exposta em JavaScript.

Configuração de bucket

Comece com os padrões certos. Um bucket mal configurado vaza arquivos quer seu RLS esteja correto ou não.

  1. Marque cada bucket como privado por padrão. No painel do Supabase → Storage → Buckets, desligue a chave Public bucket a menos que você tenha uma razão explícita (assets de marketing, avatares públicos sem PII). Buckets públicos contornam RLS para operações de leitura — qualquer um com o nome do bucket pode listar e baixar.
  2. Defina um limite rígido de tamanho de arquivo em cada bucket. Painel → Configurações do bucket → Limite de tamanho de arquivo. 50 MB é um padrão sensato para uploads de usuário; aumente deliberadamente para casos de uso de vídeo / arquivos grandes. Sem limite, um único upload malicioso pode esgotar sua cota de storage ou sua banda mensal.
  3. Restrinja tipos MIME permitidos por bucket. Lista de tipos MIME permitidos — allowlist explícita, não blocklist. image/jpeg, image/png, image/webp para buckets apenas de imagem. Nunca permita text/html, application/javascript ou image/svg+xml em um bucket de conteúdo de usuário — eles executam no navegador quando servidos via URL assinada.
  4. Use um bucket por tipo de conteúdo, não um bucket compartilhado. Configurações por bucket (tamanho, tipos MIME, políticas RLS) são a granularidade que você tem. Um bucket user-avatars, um bucket document-uploads e um bucket public-assets são mais fáceis de bloquear do que um bucket misto único.
  5. Verifique a configuração CORS se houver uploads do frontend. Se os usuários fazem upload diretamente do navegador para uma URL assinada, o CORS do bucket deve listar sua origem de produção. * só é aceitável para buckets públicos — nunca para buckets contendo PII de usuário.

Políticas RLS em storage.objects

O Supabase Storage armazena metadados de arquivos na tabela storage.objects. RLS nessa tabela controla quem pode ler, fazer upload, atualizar ou apagar arquivos. Sem RLS, a flag público/privado do bucket é sua única proteção.

  1. Confirme que RLS está habilitado em storage.objects. SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects'; deve retornar true. O Supabase o habilita por padrão em projetos novos; verifique que não foi desabilitado.
  2. Escreva uma política SELECT restrita a auth.uid() para buckets privados. CREATE POLICY "users_read_own_files" ON storage.objects FOR SELECT USING (auth.uid()::text = (storage.foldername(name))[1]);. A convenção é armazenar arquivos sob [user-id]/[filename] e usar storage.foldername() para extrair o dono do caminho.
  3. Escreva uma política INSERT que imponha a mesma convenção de caminho. CREATE POLICY "users_upload_own" ON storage.objects FOR INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);. Sem WITH CHECK, um usuário autenticado pode fazer upload para a pasta de outro usuário.
  4. Adicione políticas UPDATE e DELETE se seu app suportar edições ou exclusões de arquivos. Cada comando precisa de sua própria política. Pular DELETE significa que usuários autenticados não conseguem remover seus próprios arquivos; pular UPDATE significa que sobreposições de arquivos falham silenciosamente.
  5. Teste o acesso entre usuários em duas sessões de navegador. Entre como Usuário A, faça upload de um arquivo, copie o caminho. Entre como Usuário B em outro navegador, tente buscar o arquivo via API REST. A resposta deve ser 403 ou 404, nunca 200.
sql
-- Confirm RLS on storage.objects
SELECT rowsecurity
FROM   pg_tables
WHERE  schemaname = 'storage' AND tablename = 'objects';

-- SELECT policy: scope reads to the owning user's folder.
CREATE POLICY "users_read_own_files"
  ON storage.objects
  FOR SELECT
  USING (auth.uid()::text = (storage.foldername(name))[1]);

-- INSERT policy: enforce the [user-id]/[filename] path convention.
CREATE POLICY "users_upload_own"
  ON storage.objects
  FOR INSERT
  WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);

Validação de upload

Valide cada upload do lado do servidor, mesmo quando o bucket tem restrições de MIME e tamanho. Ferramentas de codificação com IA geram apenas validação do lado do cliente por padrão; isso não protege nada.

  1. Reverifique o tipo MIME do lado do servidor pelos bytes reais do arquivo, não pelo cabeçalho Content-Type. Use uma biblioteca como file-type (Node) ou sniffing de bytes mágicos. Um atacante pode declarar Content-Type: image/jpeg em um arquivo que na verdade é um payload poliglota HTML / JavaScript.
  2. Remova metadados EXIF de imagens enviadas. EXIF pode conter coordenadas GPS, números de série de dispositivos e timestamps. Use sharp com .withMetadata(false) ou exif-parser para limpar antes do armazenamento.
  3. Rejeite SVGs que contenham tags script ou handlers onload. SVG é XML — e muitos apps gerados por IA permitem uploads de SVG como "só uma imagem". Use DOMPurify do lado do servidor ou recuse uploads de SVG por completo.
  4. Use nomes de arquivo determinísticos e impossíveis de adivinhar. Não preserve o nome original. Use um UUID ou um hash do conteúdo do arquivo. Nomes originais vazam ("passport_scan_2024_01_15.jpg") e nomes previsíveis permitem enumeração.

URLs assinadas

URLs assinadas são como clientes acessam buckets privados. A expiração, o escopo do bucket e o que é registrado importam.

  1. Defina a expiração padrão de URLs assinadas em 1 hora ou menos. A função createSignedUrl(path, expiresIn) do SDK JS do Supabase recebe segundos. Nunca use valores como 31536000 (um ano) — a URL se torna um link semi-público permanente.
  2. Nunca armazene URLs assinadas no seu banco de dados. Gere novas do lado do servidor em cada requisição. Uma URL assinada armazenada com expiração de 1 ano que vaza via dump de banco concede acesso de longo prazo.
  3. Registre a geração de URLs assinadas, não apenas os uploads de arquivos. Se você suspeitar de comprometimento mais tarde, precisa saber quem gerou qual URL e quando. Registre auth.uid() + bucket + caminho do objeto + timestamp.
  4. Use a opção downloadAs ao servir arquivos enviados por usuários. createSignedUrl(path, expiresIn, { download: '.jpg' }) força um cabeçalho Content-Disposition: attachment para que o arquivo seja baixado em vez de renderizado — derrota a classe de execução HTML / SVG / HTML-em-PDF.

Higiene operacional

A configuração de storage deriva ao longo do tempo. Estes quatro itens operacionais mantêm a superfície apertada.

  1. Audite buckets trimestralmente. Painel → Storage → Buckets. Confirme que o estado público/privado e as listas de tipos MIME correspondem ao que o app espera. Buckets criados "temporariamente" se tornam permanentes se ninguém os remover.
  2. Monitore operações de listagem anônimas. Logs de storage (Painel → Logs → Storage) registram requisições LIST. Um pico de requisições de listagem anônimas contra um bucket privado significa que alguém está sondando-o de fora.
  3. Defina uma política de retenção para uploads efêmeros. Buckets temporários (pré-visualização de imagem, rascunhos de upload) devem se auto-deletar após 24-72 horas via função agendada. Retenção indefinida é um passivo sob as obrigações de minimização de dados da LGPD/GDPR / CCPA.
  4. Rode uma varredura do FixVibe mensalmente. A verificação baas.supabase-storage-public sonda buckets que respondem a GET + LIST anônimos. Novos buckets são adicionados; os antigos mudam de visibilidade — apenas a varredura contínua pega a deriva.

Próximos passos

Rode uma varredura do FixVibe contra sua URL de produção — listagens anônimas de storage aparecem sob baas.supabase-storage-public. Combine este checklist com Scanner de RLS Supabase para a camada de tabelas e Chave de role de serviço Supabase exposta em JavaScript para a adjacência de exposição de chaves. Para configurações incorretas de storage em outros provedores BaaS, veja Scanner de configurações incorretas de BaaS.

// 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.
  • Prompts de correção com IA em cada achado — cole de volta no Cursor / Claude Code.
Rodar varredura BaaS gratuita

sem necessidade de cadastro

Checklist de segurança de buckets Supabase Storage: 22 itens — Docs · FixVibe