Segurança
Pare de expor seu bucket! (S3 AWS, R2 Cloudflare e outros….)
Por que tratar o storage como “invisível” falha na prática: URLs diretas expõem padrões de nomes, estrutura de diretórios e superfície para scraping e enumeração — e como proxyar via backend com blob no seu domínio.
Um erro bem comum em aplicações que lidam com arquivos é tratar o storage como se fosse “invisível”. Na prática, muita gente entrega arquivo assim:
https://bucket.s3.amazonaws.com/arquivo.pdf ou alguma variação disso com CDN ou signed URL…
Funciona? Sim. Mas tem um detalhe importante: você está expondo diretamente uma parte sensível da sua infraestrutura, e isso abre algumas superfícies que normalmente passam despercebidas.
O problema não é só o acesso ao arquivo. Quando você expõe o bucket, a depender da sua configuração você pode estar revelando:
- Padrões de nomes (ex.:
user123/invoice,user1234/invoice). - Estrutura de diretórios (
users/upload/profile/photo.png). - Existência de mais arquivos (
invoice2024,invoice2025,invoice2026). - Comportamento de storage (headers, respostas, etc.).
Entendi, mas qual o problema disso?
Com as informações acima, um atacante pode, com facilidade:
- Tentar adivinhar outros arquivos.
- Automatizar scraping.
- Mapear como sua aplicação organiza dados.
- Explorar possíveis falhas de permissão.
Não é um ataque sofisticado: é exploração básica de uma superfície exposta.
Manter assim é como deixar a porta da sua casa aberta e dizer: pode entrar, vá até a dispensa, pega o que precisa, só não mexe no resto.
“Mas eu uso signed URL na minha aplicação”
Você reduz o risco, mas ainda fica exposto. Signed URL não elimina o problema porque:
- A origem ainda fica visível.
- O padrão de URLs continua exposto.
- Ainda existe uma janela de exploração enquanto a URL está válida.
Agora sua porta tem chave temporária, e você diz: pode entrar na dispensa só por cinco minutos. A pessoa ainda sabe onde fica a casa, ainda pode compartilhar a chave com terceiros e ainda pode voltar enquanto a chave for válida.
A mudança de abordagem
Em vez de deixar o storage “visível”, a ideia é simples: o cliente não deve saber que o bucket existe.
Fluxo enxuto:
- Cliente faz uma requisição autenticada.
- Backend valida acesso.
- Backend busca o arquivo no storage.
- Frontend consome e expõe via
blob:sob o seu domínio.
O storage (bucket) vira apenas um detalhe interno.
Backend (Node/Express de exemplo): valida sessão, lê o arquivo no S3/R2 com SDK ou signed URL interna, e devolve o stream com Content-Type adequado.
// Conceito: rota autenticada — o cliente nunca vê a URL do bucket
app.get("/api/files/:id", requireAuth, async (req, res) => {
const file = await resolveFileForUser(req.user.id, req.params.id);
if (!file) return res.sendStatus(404);
const stream = await storage.getObjectStream(file.storageKey);
res.setHeader("Content-Type", file.mimeType);
res.setHeader(
"Content-Disposition",
`inline; filename="${encodeURIComponent(file.name)}"`,
);
stream.pipe(res);
});
Frontend — usar a URL da sua API e gerar blob para exibir ou baixar:
async function openPrivateFile(fileId: string) {
const res = await fetch(`/api/files/${fileId}`, {
credentials: "include",
});
if (!res.ok) throw new Error("Sem permissão ou arquivo inexistente");
const blob = await res.blob();
const url = URL.createObjectURL(blob);
window.open(url, "_blank", "noopener");
// Depois: URL.revokeObjectURL(url) conforme o caso
}
Um toque especial para fechar bem
O bucket continua privado. Quando o backend precisa ler do provedor, ele pode usar signed URL só no servidor, com:
- Validade curta (minutos).
- Escopo limitado a um único objeto.
- Bônus: renomear ou rotacionar keys para não vazar padrão.
Essa URL é usada apenas internamente para fetch/stream. O cliente nunca vê — e se vazar por engano, expira rápido e não abre outros arquivos.
O que isso muda na prática
Você reduz bastante a superfície exposta:
- Não há URL pública óbvia do storage.
- Não há padrão visível de arquivos na barra de endereços do usuário.
- Fica mais difícil automatizar enumeração de fora.
- Toda entrega passa por validação no seu backend.
Não é sobre esconder por esconder — é sobre reduzir superfície de ataque.
Nem tudo são flores
Essa abordagem tem trade-offs, como tudo na programação:
- Mais tráfego passando pela sua API.
- Mais carga no backend.
- Mais complexidade operacional.
Em alguns casos compensa cache (CDN privada, edge) ou fila dedicada para downloads pesados. Algumas equipes separam uma API só de arquivos para não misturar com o core do negócio.
O critério é seu. Em geral, vale especialmente quando você tem:
- Arquivos privados ou dados sensíveis.
- Necessidade de não revelar infraestrutura de storage na borda.
Agora que você conhece esse risco silencioso, vale aprofundar e escolher o desenho certo para o seu projeto.