Pular para o conteúdo

Noções de segurança

Seu bucket do Supabase Storage é público. Isso é um problema?

Um bucket público do Supabase Storage significa que quem tem a URL de um arquivo pode abri-lo. Não significa que alguém possa listar o que está lá dentro.

Vlad Tkachenko8 min de leitura
Documentos dentro de um contêiner. Uma barra cobre quase toda a abertura e para antes do fim, deixando quatro deles no vão.

Em resumo

  • Um bucket público do Supabase Storage significa uma coisa só: quem tem a URL de um arquivo pode abri-lo sem fazer login. Não diz nada sobre se essa pessoa consegue ver o que mais está lá dentro.
  • Quem concede a listagem é uma política de acesso e não a chave de público, então um bucket privado pode ser listável e um público pode não ser.
  • A listagem é o que precisa ser resolvido hoje, porque poupa um desconhecido do trabalho de adivinhar um único nome de arquivo.

Alguém abriu seu aplicativo, ficou um minuto fuçando nele e mandou uma mensagem: seu bucket do Supabase Storage está público, e qualquer pessoa pode listar o que há lá dentro.

São duas afirmações separadas. Uma delas trata provavelmente de uma configuração que você escolheu de propósito e deveria manter. A outra vale uma tarde.

Aqui está a parte que guia após guia conta errado: público e listável são duas chaves diferentes, e a preocupante não é a que todo mundo manda você virar. Tornar um bucket privado fecha a primeira. Pode deixar a segunda escancarada, e um bucket que nunca foi público na vida pode estar listável hoje à tarde.

Um bucket público do Supabase Storage é um problema de segurança?

Por si só, não. Significa uma coisa bem específica, e essa coisa é muito frequentemente a que você queria.

Marcar um bucket como público dá a cada arquivo dentro dele uma URL que funciona sem login. É toda a funcionalidade. É assim que o avatar em um comentário carrega para quem não tem conta, assim que seu logotipo aparece em um e-mail, assim que alguém que ainda está decidindo se cria conta vê a foto de um produto.

Um bucket público se parece mais com um telefone fora da lista do que com uma porta destrancada. A linha atende qualquer um que disque, e não existe catálogo onde procurar o número. Se isso está bem depende de uma única pergunta: com que facilidade alguém chegaria a esse número sem que ninguém o entregasse.

O Supabase cria buckets novos como privados e coloca um aviso ao lado da chave. Um bucket público é, portanto, algo que alguém ligou: você, ou seu builder, para fazer um recurso de upload funcionar.

O que a chave de público realmente liga

Uma linha da tabela abaixo, e nada mais dela.

O que alguém tentaBucket públicoBucket privado
Abrir um arquivo cuja URL exata já temFunciona, sem loginExige uma política ou link assinado
Pedir a lista do conteúdo do bucketSó se uma política permitirSó se uma política permitir
Enviar um arquivoSó se uma política permitirSó se uma política permitir
Apagar ou substituir um arquivoSó se uma política permitirSó se uma política permitir

A própria página de ajuda do Supabase diz isso com toda a clareza possível: um bucket público significa que existe uma URL pública com a qual baixar o arquivo, e qualquer outra operação continua tendo de satisfazer as políticas daquele bucket.

Ou seja, a chave é um controle menor do que o nome sugere. Três daquelas quatro linhas são decididas em outro lugar por completo, em Storage → Policies, e a mensagem que você recebeu era quase com certeza sobre a segunda.

A chave em cima muda uma coisa. A caixa embaixo dela decide se um desconhecido recebe a lista, e faz isso do mesmo jeito quer o bucket seja público ou privado.

Por que um bucket privado ainda pode ser listável

Porque quem concede a listagem é uma política de acesso, e a política que quase todo mundo cola concede isso a todos.

Toda leitura do Storage passa por Row Level Security em uma tabela chamada storage.objects. Listar um bucket é uma leitura, arquivada sob SELECT, exatamente como baixar um arquivo. O próprio guia rápido do Supabase mostra uma política com esta forma, de onde as cópias costumam sair:

create policy "Public Access"
  on storage.objects for select
  using ( bucket_id = 'public' );

Leia o que ela diz. Qualquer requisição pode ler qualquer coisa naquele bucket. Ela não pergunta quem está perguntando, e não pergunta se o arquivo tem alguma relação com quem pede. Ler abrange baixar um arquivo, e abrange entregar a lista de arquivos, porque são a mesma permissão usando dois chapéus.

A chave do próprio bucket nunca entra nisso. Uma política assim em um bucket privado torna o bucket listável, e o painel vai continuar descrevendo-o, com toda a razão, como privado.

A palavra que causa o estrago é «qualquer». Uma requisição levando a chave publicável que viaja dentro do código do seu aplicativo satisfaz aquela política sem esforço, e essa chave existe para ser legível por todos, que é precisamente o motivo pelo qual quem deveria estar trabalhando é a política. É a mesma forma do problema ao lado, onde o Row Level Security está ligado e mesmo assim permite tudo.

O que alguém tira de uma lista dos seus arquivos

Nomes, principalmente. O que é bem mais do que parece.

Nomes de arquivo costumam descrever o conteúdo, porque alguém os escolheu pensando no que havia dentro: fatura-marco-acme.pdf, passaporte-frente.jpg, folha-final-v2.xlsx. Uma lista desses é um resumo razoável do seu negócio, e o tamanho dela diz mais ou menos quantos clientes você tem. Se o bucket também for público, cada nome dessa lista é um link que funciona.

De volta ao telefone. Estar fora da lista vale alguma coisa até o dia em que o catálogo sai, e depois disso nunca importou o quanto o número era difícil de adivinhar.

Sem listagem, um desconhecido precisa chegar a um nome de arquivo de algum jeito. Com ela, entregam a ele cada nome de arquivo que você tem.

O jeito como isso aparece é bem comum. Um documento que pertence a um cliente surge na frente de outro. Alguém repete para você o nome de um arquivo que não tinha como conhecer. Uma pasta de uploads é copiada inteira por um rastreador automático que estava lendo todos os projetos Supabase que conseguia encontrar.

Nossa varredura gratuita pede uma listagem ao seu Storage usando nada além da chave que já está no código do seu aplicativo, e diz quais buckets responderam com conteúdo. Ela lê nomes e nunca baixa um arquivo. Leva cerca de 20 segundos e não pede conta: escaneie seu aplicativo.

Como saber em qual dos dois casos você está

Duas conferências no painel do Supabase, e a segunda é sobre o que a mensagem realmente tratava.

Storage → Buckets. Os públicos vêm marcados como públicos. Para cada um, pergunte se absolutamente todo arquivo ali dentro é algo que você mostraria a um desconhecido. Não a maioria dos arquivos. Todos, incluindo o que cair lá na semana que vem por um recurso que você ainda não construiu.

Storage → Policies. Leia cada política SELECT que toca o bucket. Uma condição que menciona apenas o nome do bucket deixa qualquer um ler. Uma condição que compara o dono do arquivo com quem faz a requisição está fazendo trabalho de verdade. Uma lista de políticas vazia em um bucket privado significa que nada é lido, o que é restritivo e seguro.

Se seu aplicativo precisa mostrar um arquivo privado à pessoa certa, a ferramenta para isso é um link assinado: seu servidor pede ao Supabase uma URL que funciona por um número fixo de minutos e depois para de funcionar. Assim o arquivo segue privado e ainda assim chega à página.

Quando público é a resposta certa

Mais vezes do que um artigo de segurança costuma admitir, e vale dizer isso.

Se o arquivo é para todo mundo, público está certo, e contornar isso compra um aplicativo mais lento e mais código para manter. Avatares, logotipos, imagens de capa, tudo o que um visitante deslogado deve ver: em um bucket público, e pare de pensar no assunto.

A única coisa a fazer mesmo ali é deixar a listagem desligada. Um bucket público cheio de avatares é um recurso. O mesmo bucket entregando a lista completa de todos que já enviaram um é outra coisa, e você não pediu por ela.

Se o arquivo pertence a uma pessoa específica, ele vai para um bucket privado com uma política que confere quem está pedindo, e chega à página por um link assinado. O teste é uma pergunta só: para quem é este arquivo? Para todo mundo, ou para uma conta com nome.

O que fazer esta semana

O que fazer

  • Abra Storage → Buckets e anote quais estão públicos. Em cada um, decida se ele guarda arquivos destinados a todos ou arquivos destinados a uma pessoa só.
  • Leia cada política SELECT em storage.objects. Uma condição que nomeia apenas o bucket entrega a lista de arquivos a qualquer um; foi ela que tornou verdadeira a mensagem que você recebeu.
  • Mova tudo que pertence a um único usuário para um bucket privado e sirva por links assinados, que expiram sozinhos.
  • Pare de nomear arquivos enviados conforme o conteúdo deles. Um identificador aleatório não custa nada e faz uma lista vazada entregar bem menos do que entregaria.
  • Faça backup do seu Storage separado do banco de dados. Seus uploads não estão nem no seu código nem nas suas tabelas Postgres, então nada que copie esses dois está copiando seus arquivos.

Abra Storage → Policies no seu projeto e leia o que está lá antes de mexer em uma única chave. Aquela tela é o que decide se a segunda metade da mensagem que você recebeu era verdadeira. A lista de segurança de 10 minutos cobre isso junto com o resto do que um aplicativo recém-lançado costuma deixar aberto, e se um bucket realmente estava legível, o que um desconhecido alcança no seu banco de dados é a próxima leitura, já que a política que entrega uma lista de arquivos tem a mesma forma da que entrega uma tabela.

Perguntas frequentes

Disseram que meu bucket do Supabase Storage está público. Devo torná-lo privado?

Não antes de saber o que há nele. Buckets públicos existem por um motivo: fotos de perfil, logotipos, imagens de produto e tudo o mais que seu aplicativo mostra a um visitante que não fez login. Para esses arquivos, público é a configuração certa. Para faturas, documentos de identidade, exportações ou qualquer coisa que pertença a uma pessoa específica, é a errada, e o que você quer então é privado mais links assinados. A pergunta nunca foi se público é ruim. É se aqueles arquivos em particular foram feitos para ser vistos por qualquer um que peça.

Qual é a diferença entre um bucket público e um listável?

Público decide se um arquivo abre para quem já tem a URL dele. Listável decide se um desconhecido pode pedir ao seu projeto o conteúdo inteiro de um bucket e receber resposta. Os dois são configurados em lugares diferentes do painel do Supabase, e o segundo é o que transforma um palpite em um índice.

Alguém consegue adivinhar as URLs dos arquivos no meu bucket público?

Depende inteiramente de como seu aplicativo os nomeia. Se ele envia com o nome original, ou com algo organizado como fatura-4.pdf, então sim, e dá pouquíssimo trabalho. Se envia sob um identificador longo e aleatório, adivinhar deixa de ser prático. É exatamente por isso que a listagem pesa tanto: ela remove o passo de adivinhar por completo.

Como impeço que listem meu bucket?

A listagem passa por uma política de Row Level Security na tabela storage.objects, então é ali que se resolve e não no bucket em si. Abra Storage → Policies no painel do Supabase e leia cada política SELECT que toca o bucket. Uma cuja única condição é o nome do bucket deixa qualquer requisição ler aquele bucket, lista de arquivos incluída. Estreite-a para que compare o arquivo com quem está pedindo, ou remova-a e distribua links assinados a partir do seu servidor.

Meu backup do banco de dados cobre os arquivos enviados?

Não. O Storage fica fora do seu banco Postgres, então um backup do banco contém as linhas que apontam para seus arquivos e nenhum dos arquivos. Restaurá-lo devolve uma tabela cheia de links para coisas que não estão mais lá. Seus arquivos enviados precisam ser copiados à parte, e a maioria das pessoas descobre isso no dia em que importa.

Escrito por

Vlad Tkachenko

Fundador da Reeve

Passo meus dias olhando apps criados com Lovable, Bolt, v0, Cursor e Replit, e a curta lista de erros que aparecem neles sem parar.

Mais sobre o autor

Leia depois

Não sabe como está o seu próprio app?

Faça um scan gratuito e receba uma nota clara de A a F em cerca de 20 segundos. Sem conta e sem cartão.

Verificar meu app grátis

Verificação externa automatizada, não uma auditoria completa. A ausência de achados não é garantia de segurança.