// 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.
- 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.
- 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.
- Restrinja tipos MIME permitidos por bucket. Lista de tipos MIME permitidos — allowlist explícita, não blocklist.
image/jpeg,image/png,image/webppara buckets apenas de imagem. Nunca permitatext/html,application/javascriptouimage/svg+xmlem um bucket de conteúdo de usuário — eles executam no navegador quando servidos via URL assinada. - 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 bucketdocument-uploadse um bucketpublic-assetssão mais fáceis de bloquear do que um bucket misto único. - 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.
- Confirme que RLS está habilitado em storage.objects.
SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects';deve retornartrue. O Supabase o habilita por padrão em projetos novos; verifique que não foi desabilitado. - 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 usarstorage.foldername()para extrair o dono do caminho. - 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. - 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.
- 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
403ou404, nunca200.
-- 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.
- Reverifique o tipo MIME do lado do servidor pelos bytes reais do arquivo, não pelo cabeçalho
Content-Type. Use uma biblioteca comofile-type(Node) ou sniffing de bytes mágicos. Um atacante pode declararContent-Type: image/jpegem um arquivo que na verdade é um payload poliglota HTML / JavaScript. - Remova metadados EXIF de imagens enviadas. EXIF pode conter coordenadas GPS, números de série de dispositivos e timestamps. Use
sharpcom.withMetadata(false)ouexif-parserpara limpar antes do armazenamento. - Rejeite SVGs que contenham tags
scriptou handlersonload. SVG é XML — e muitos apps gerados por IA permitem uploads de SVG como "só uma imagem". UseDOMPurifydo lado do servidor ou recuse uploads de SVG por completo. - 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.
- 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 como31536000(um ano) — a URL se torna um link semi-público permanente. - 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.
- 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. - Use a opção
downloadAsao servir arquivos enviados por usuários.createSignedUrl(path, expiresIn, { download: '.jpg' })força um cabeçalhoContent-Disposition: attachmentpara 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.
- 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.
- 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. - 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.
- Rode uma varredura do FixVibe mensalmente. A verificação
baas.supabase-storage-publicsonda buckets que respondem aGET+LISTanô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.
