// 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 ao nível da linha que a base de dados. Isso significa que as mesmas armadilhas de RLS que afetam tabelas afetam o acesso a ficheiros — 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 secçõ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 defaults certos. Um bucket mal configurado vaza ficheiros quer o seu RLS esteja correto ou não.
- Marque cada bucket como privado por defeito. No painel do Supabase → Storage → Buckets, desligue o interruptor Public bucket a menos que 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 descarregar.
- Defina um limite rígido de tamanho de ficheiro em cada bucket. Painel → Configurações do bucket → Limite de tamanho de ficheiro. 50 MB é um padrão sensato para uploads de utilizador; aumente deliberadamente para casos de uso de vídeo / ficheiros grandes. Sem limite, um único upload malicioso pode esgotar a sua cota de storage ou a 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+xmlnum bucket de conteúdo de utilizador — eles executam no navegador quando servidos via URL assinada. - Use um bucket por tipo de conteúdo, não um bucket partilhado. Configurações por bucket (tamanho, tipos MIME, políticas RLS) são a granularidade que 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 utilizadores fazem upload diretamente do navegador para uma URL assinada, o CORS do bucket deve listar a sua origem de produção.
*só é aceitável para buckets públicos — nunca para buckets com PII de utilizador.
Políticas RLS em storage.objects
O Supabase Storage armazena metadados de ficheiros na tabela storage.objects. RLS nessa tabela controla quem pode ler, fazer upload, atualizar ou apagar ficheiros. Sem RLS, a flag público/privado do bucket é a sua única proteção.
- Confirme que RLS está habilitado em storage.objects.
SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects';deve devolvertrue. O Supabase habilita-o por defeito 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 ficheiros 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 utilizador autenticado pode fazer upload para a pasta de outro utilizador. - Adicione políticas UPDATE e DELETE se a sua app suportar edições ou eliminações de ficheiros. Cada comando precisa da sua própria política. Saltar DELETE significa que utilizadores autenticados não conseguem remover os seus próprios ficheiros; saltar UPDATE significa que sobreposições de ficheiros falham silenciosamente.
- Teste o acesso entre utilizadores em duas sessões de navegador. Entre como Utilizador A, faça upload de um ficheiro, copie o caminho. Entre como Utilizador B noutro navegador, tente obter o ficheiro 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 defeito; isso não protege nada.
- Reverifique o tipo MIME do lado do servidor pelos bytes reais do ficheiro, 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/jpegnum ficheiro 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 muitas apps geradas por IA permitem uploads de SVG como "só uma imagem". UseDOMPurifydo lado do servidor ou recuse uploads de SVG por completo. - Use nomes de ficheiro determinísticos e impossíveis de adivinhar. Não preserve o nome original. Use um UUID ou um hash do conteúdo do ficheiro. Nomes originais vazam ("
passport_scan_2024_01_15.jpg") e nomes previsíveis permitem enumeração.
URLs assinadas
URLs assinadas são como clientes acedem a buckets privados. A expiração, o escopo do bucket e o que é registado 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 torna-se um link semi-público permanente. - Nunca armazene URLs assinadas na sua base de dados. Gere novas do lado do servidor em cada pedido. Uma URL assinada armazenada com expiração de 1 ano que vaze via dump de base de dados concede acesso de longo prazo.
- Registe a geração de URLs assinadas, não apenas os uploads de ficheiros. Se suspeitar de comprometimento mais tarde, precisa de saber quem gerou qual URL e quando. Registe
auth.uid()+ bucket + caminho do objeto + timestamp. - Use a opção
downloadAsao servir ficheiros enviados por utilizadores.createSignedUrl(path, expiresIn, { download: '.jpg' })força um cabeçalhoContent-Disposition: attachmentpara que o ficheiro seja descarregado 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 a app espera. Buckets criados "temporariamente" tornam-se permanentes se ninguém os remover.
- Monitorize operações de listagem anónimas. Logs de storage (Painel → Logs → Storage) registam pedidos
LIST. Um pico de pedidos de listagem anónimos contra um bucket privado significa que alguém o está a sondar 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 auto-apagar-se após 24-72 horas via função agendada. Retenção indefinida é uma responsabilidade ao abrigo das obrigações de minimização de dados do RGPD / CCPA.
- Execute um varrimento 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 o varrimento contínuo apanha a deriva.
Próximos passos
Execute um varrimento do FixVibe contra a 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 noutros fornecedores BaaS, veja Scanner de configurações incorretas de BaaS.
