Pular para o conteúdo

Buckets públicos no Supabase Storage: os arquivos dos seus usuários estão expostos?

Avatares podem ser públicos; documentos, notas fiscais e contratos, não. Como revisar os buckets e as regras do Supabase Storage e corrigir sem quebrar nada.

4 min de leitura

A maioria dos apps feitos com Lovable, Bolt e ferramentas parecidas deixa o usuário enviar alguma coisa: uma foto de perfil, um comprovante, um contrato, a foto de um documento para verificação. No Supabase, esses arquivos vão para o Storage, organizados em buckets.

Quando uma ferramenta de IA cria um bucket, muitas vezes ela o deixa público, porque assim as imagens aparecem sem trabalho extra. Para um logo, tudo bem. Para qualquer coisa pessoal, significa que qualquer pessoa com o link baixa o arquivo, e às vezes qualquer pessoa consegue listar todos os arquivos do bucket.

Este guia é a metade “Storage” da nossa verificação de 10 minutos no Supabase. Leva mais ou menos o mesmo tempo.

Buckets públicos e privados

Bucket público Bucket privado (o padrão)
Baixar um arquivo Qualquer pessoa com a URL, sem checar regras Só se uma regra permitir, ou com um link assinado que expira
Enviar, apagar, mover Controlado pelas suas regras Controlado pelas suas regras
Listar os arquivos Controlado pelas suas regras Controlado pelas suas regras

Duas coisas decorrem dessa tabela:

  1. Um bucket público ignora completamente as suas regras nos downloads. Se os caminhos dos arquivos são previsíveis (invoices/1043.pdf, ids/user-15.jpg), um estranho consegue adivinhá-los.
  2. Deixar o bucket privado não adianta se as regras em storage.objects deixam qualquer um ler tudo. As regras do Storage são políticas comuns de Row Level Security nessa tabela, e uma regra generosa abre um bucket privado do mesmo jeito.

Passo 1: liste seus buckets

No painel do Supabase, abra o SQL Editor e rode:

Buckets, visibilidade e limites de upload
select id, public, file_size_limit, allowed_mime_types
from storage.buckets
order by public desc, id;

Para cada linha com public = true, faça uma pergunta: eu ficaria tranquilo se todos os arquivos deste bucket aparecessem num buscador? Imagens de produtos, sim. Qualquer coisa que um usuário enviou sobre si mesmo, não.

Olhe também as duas últimas colunas. Limites vazios significam que qualquer pessoa autorizada a enviar pode guardar um arquivo de qualquer tamanho e qualquer tipo no seu projeto, por sua conta.

Passo 2: leia as regras do Storage

Todas as regras em storage.objects
select policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'storage' and tablename = 'objects'
order by cmd, policyname;

O que procurar:

  • Uma regra SELECT para anon ou public cuja condição é só bucket_id = '…'. Ela deixa qualquer pessoa listar e ler todos os arquivos do bucket.
  • Uma regra INSERT para anon, ou para authenticated sem condição de pasta. Ela deixa qualquer usuário enviar arquivos em qualquer lugar, inclusive por cima dos caminhos de outros usuários, se uma regra UPDATE também estiver aberta.
  • Uma regra DELETE que não liga o arquivo ao seu dono.

O Security Advisor do painel também aponta alguns desses casos. Confira depois de qualquer mudança.

Passo 3: confirme de fora

Usando só a URL do projeto e a chave pública (em Project Settings → API Keys), tente listar um bucket como um estranho faria:

Listar arquivos de um bucket com a chave pública
curl -X POST "https://YOUR-PROJECT.supabase.co/storage/v1/object/list/BUCKET_NAME" \
  -H "apikey: YOUR_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prefix": "", "limit": 20}'

Se isso devolver nomes de arquivos de um bucket que guarda arquivos privados, a listagem está aberta. Repita para cada bucket que guarde algo pessoal.

Como corrigir

Leve os arquivos pessoais para um bucket privado

Crie um bucket privado (por exemplo, documents) para tudo que identifica uma pessoa ou contém dados do negócio. Mantenha um bucket público só para arquivos que devem mesmo ser públicos.

Depois, mude a forma como o app mostra esses arquivos. Em vez de uma URL pública permanente, ele pede ao Supabase uma URL assinada que expira, por exemplo em uma hora. No cliente JavaScript, isso é createSignedUrl(path, 3600). Os links públicos antigos param de funcionar quando você troca, então planeje essa mudança junto com a mudança no código, não antes.

Dê a cada usuário a sua própria pasta

O padrão mais comum é enviar cada arquivo dentro de uma pasta com o ID do usuário, como documents/8f1c…/contract.pdf, e escrever regras que conferem isso:

Usuários só leem e enviam na própria pasta
create policy "Read own documents"
on storage.objects for select
to authenticated
using (
  bucket_id = 'documents'
  and (storage.foldername(name))[1] = (select auth.uid()::text)
);

create policy "Upload to own folder"
on storage.objects for insert
to authenticated
with check (
  bucket_id = 'documents'
  and (storage.foldername(name))[1] = (select auth.uid()::text)
);

Adicione regras de update e delete com a mesma condição se os usuários puderem substituir ou remover seus arquivos. Enviar com a opção de sobrescrever ligada exige select, insert e update.

Buckets públicos: leitura sim, listagem não

Um bucket público de avatares não precisa de nenhuma regra SELECT para os downloads, já que as URLs públicas ignoram as regras. Remova regras SELECT amplas para anon em buckets públicos, a menos que o app realmente precise listar o conteúdo deles.

Defina limites

Dê a cada bucket um tamanho máximo de arquivo e uma lista de tipos permitidos (por exemplo, image/png, image/jpeg, application/pdf). Leva um minuto nas configurações do bucket e impede que o seu Storage vire hospedagem grátis para outra pessoa.

Se o seu app lida com documentos, identidades ou registros de pagamento e você quer que alguém olhe com você, o nosso Security Check gratuito revisa o Storage junto com as regras do banco, as chaves e a autenticação, sem alterar nada e de fora.

SupabaseRow Level SecurityApps feitos com IA

Este artigo também está disponível em: EnglishEspañol日本語中文

Todos os artigos