Fazer uploads para S3 no Node.js permite armazenar imagens, documentos, backups e arquivos grandes fora do servidor da aplicação. Object storage oferece durabilidade, escalabilidade, versionamento, políticas de acesso e integração com CDN.
O fluxo seguro não consiste apenas em chamar PutObject. A aplicação precisa gerar keys não previsíveis, limitar tamanho, validar tipo, usar credenciais de workload, evitar buckets públicos, configurar criptografia, tratar multipart uploads, limpar objetos órfãos e controlar downloads.
Neste guia, você aprenderá a configurar o AWS SDK v3, enviar streams, criar URLs pré-assinadas, validar uploads diretos, aplicar políticas, usar multipart, proteger metadados, processar arquivos e testar o fluxo.
O que é Amazon S3?
O Amazon S3 armazena objetos em buckets. A documentação de exemplos do AWS SDK para JavaScript v3 mostra operações como PutObject, GetObject e multipart. A documentação de segurança do S3 reúne práticas de acesso e proteção.
Objeto
Um objeto possui:
- bucket;
- key;
- conteúdo;
- metadados;
- tipo;
- ETag;
- versão, quando habilitada.
Instalando o SDK
npm install @aws-sdk/client-s3 \
@aws-sdk/s3-request-presignerO SDK v3 usa pacotes modulares e commands.
Criando o cliente
import { S3Client } from '@aws-sdk/client-s3';
const s3 = new S3Client({
region: process.env.AWS_REGION
});Quando executado na AWS, o SDK pode usar a identidade do workload. Evite colocar access key e secret no código.
Credenciais
Use IAM role, task role, service account ou mecanismo equivalente. Consulte Gestão de Segredos no Node.js.
Privilégio mínimo
A role da API pode precisar apenas de:
s3:PutObjectem um prefixo;s3:GetObjectquando necessário;s3:AbortMultipartUpload;- operações multipart específicas.
Ela não precisa listar ou excluir todo o bucket.
Key do objeto
tenants/{tenantId}/uploads/{uuid}Use IDs gerados pelo servidor. Não use o nome original como caminho.
Path traversal
S3 não possui diretórios reais, mas keys com ../ podem confundir aplicações e políticas. Construa a key com segmentos controlados.
Nome original
Guarde como metadado no banco, depois de limitar tamanho e remover caracteres problemáticos. Escape ao mostrar na interface.
Upload pelo servidor
import { PutObjectCommand } from '@aws-sdk/client-s3';
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: fileStream,
ContentType: detectedMimeType,
Metadata: {
tenant: tenantId,
upload: uploadId
}
}));Prefira stream para não carregar arquivos inteiros em memória.
Backpressure
Streams permitem que leitura e envio avancem no ritmo da rede. Consulte Streams no Node.js.
Limite de tamanho
Defina limite no parser, na rota e na política do upload. Não confie apenas no header Content-Length.
Contagem real
Conte bytes durante o stream e interrompa quando exceder o máximo.
Content-Type
O tipo enviado pelo cliente não é confiável. Detecte assinatura do arquivo quando a segurança depender do formato.
Extensão
A extensão é apenas parte do nome. Um arquivo .jpg pode conter HTML ou executável.
Uploads diretos
Para arquivos grandes, o frontend pode enviar diretamente ao S3 com URL pré-assinada. A API não precisa transportar todos os bytes.
Gerando URL pré-assinada
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const command = new PutObjectCommand({
Bucket: bucket,
Key: key,
ContentType: expectedContentType
});
const url = await getSignedUrl(s3, command, {
expiresIn: 300
});Prazo curto
Use poucos minutos. Uma URL vazada permanece válida até expirar.
Key criada pelo servidor
O cliente não deve escolher livremente bucket e key. A API cria um upload pendente e retorna o destino autorizado.
Registro de upload
CREATE TABLE uploads (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
object_key TEXT NOT NULL UNIQUE,
status TEXT NOT NULL,
expected_size BIGINT,
content_type TEXT,
created_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ NOT NULL
);Confirmação
Depois do envio, o cliente chama uma rota de confirmação. A API usa HeadObject para conferir existência, tamanho e metadados.
Não confie no callback do cliente
O cliente pode afirmar que enviou um arquivo diferente. Consulte o S3 e compare com o registro pendente.
Presigned POST
Presigned POST permite impor condições como tamanho, prefixo e tipo. É útil quando o navegador usa formulário multipart diretamente com S3.
Condições
Defina:
- bucket fixo;
- prefixo;
- limite de tamanho;
- Content-Type esperado;
- prazo;
- metadados obrigatórios.
CORS do bucket
Uploads diretos do navegador exigem CORS restrito às origens necessárias. Não use * com métodos e headers amplos sem necessidade.
Bucket privado
Mantenha Block Public Access habilitado. Entregue downloads por URL assinada, CDN privada ou endpoint autorizado.
Download autorizado
const command = new GetObjectCommand({
Bucket: bucket,
Key: objectKey
});
const url = await getSignedUrl(s3, command, {
expiresIn: 60
});Verifique tenant e permission antes de gerar a URL.
Content-Disposition
Para downloads, defina nome seguro:
ResponseContentDisposition:
'attachment; filename="documento.pdf"'Sanitize o nome e evite quebra de header.
Arquivos públicos
Se imagens precisam ser públicas, prefira CDN com origin privado e políticas controladas, em vez de tornar todo o bucket público.
Cache-Control
Objetos imutáveis com key versionada podem usar cache longo. Arquivos privados não devem receber política pública.
Criptografia
S3 oferece criptografia no servidor. Para chaves controladas por KMS, configure a key e permissões apropriadas.
KMS
A role precisa de acesso à chave apenas para o prefixo e operação necessários. Monitore erros de throttling e permissão.
Criptografia no cliente
Dados extremamente sensíveis podem ser cifrados antes do upload. Isso exige gerenciamento de chaves, metadados e recuperação.
Versionamento
Habilitar versioning protege contra sobrescrita e exclusão acidental, mas aumenta armazenamento. Defina lifecycle para versões antigas.
Object Lock
Backups e auditorias podem usar retenção imutável. Configure com requisitos legais e operacionais claros.
Multipart upload
Arquivos grandes podem ser divididos em partes. O fluxo cria o upload, envia partes e completa.
Vantagens
- retomar falhas;
- paralelizar partes;
- evitar reiniciar o arquivo;
- suportar objetos grandes.
Partes paralelas
Limite concorrência. Enviar dezenas de partes ao mesmo tempo pode saturar memória, sockets e banda.
ETag das partes
Guarde o ETag retornado para completar o multipart. A ordem e o número das partes precisam corresponder.
AbortMultipartUpload
Se o fluxo falha, aborte. Uploads incompletos consomem armazenamento.
Lifecycle para incompletos
Configure regra para excluir multipart uploads abandonados após alguns dias.
Retry
O SDK aplica estratégias, mas operações precisam ser idempotentes. Repetir PutObject na mesma key sobrescreve o objeto.
Key imutável
Use key única por upload para evitar sobrescrita acidental. Alterações criam novo objeto e atualizam a referência no banco.
Checksums
Use checksum suportado para verificar integridade em trânsito. ETag não é sempre MD5, especialmente em multipart e criptografia.
Antivírus
Arquivos recebidos podem ser colocados em prefixo de quarentena. Um worker analisa e move ou marca como aprovado.
Não servir antes da análise
O status do banco deve impedir download enquanto o arquivo estiver pendente.
Processamento de imagem
Reencode imagens, limite dimensões e remova metadados quando necessário. Execute bibliotecas nativas em ambiente isolado.
Arquivos HTML e SVG
Podem executar scripts quando servidos inline. Use attachment, domínio separado e políticas de Content-Type.
SSRF
Se a aplicação importa arquivos por URL, aplique as defesas de SSRF no Node.js.
Multi-tenancy
Inclua tenant no prefixo e nas políticas. O banco deve localizar objeto por tenant e ID, não por key recebida do usuário.
Veja Multi-Tenancy no Node.js.
Auditoria
Registre criação, confirmação, análise, download, exclusão e mudança de retenção. Consulte Logs de Auditoria no Node.js.
Logs
Registre bucket lógico, key parcial, upload ID, tamanho, duração e request ID. Não registre URL pré-assinada completa.
Consulte Logs com Pino no Node.js.
Métricas
Monitore bytes enviados, duração, falhas, multipart incompletos, objetos em quarentena, erros de KMS e URLs geradas.
Rate limiting e quotas
Limite uploads por tenant, quantidade, tamanho diário e armazenamento total. Consulte Rate Limiting no Node.js.
Custos
Considere armazenamento, requests, transferência, KMS, lifecycle e CDN. Um upload abusivo pode gerar custo mesmo sem derrubar a API.
Limpeza de órfãos
Compare uploads pendentes e objetos sem referência. Exclua somente após janela e auditoria.
Exclusão
Apagar o registro do banco e esquecer o S3 gera vazamento e custo. Use job idempotente e status de exclusão.
Backup
S3 não substitui automaticamente backup e retenção. Versionamento, replicação e Object Lock atendem necessidades diferentes.
Teste com emulador
MinIO ou LocalStack ajudam no desenvolvimento, mas mantenha testes contra o serviço real para políticas, KMS e presigned URLs.
Teste de upload direto
Gere a URL, envie o arquivo, confirme HeadObject e valide o registro.
Teste de tamanho
Tente enviar acima do limite. A política e a aplicação devem rejeitar.
Teste de tenant
Um usuário do tenant A não deve gerar download para key do tenant B.
Teste de Content-Type
Envie HTML com extensão de imagem e confirme quarentena ou rejeição.
Teste de URL expirada
Use relógio controlado e confirme que a URL não funciona depois do prazo.
Teste multipart
Interrompa após algumas partes, retome e confirme conclusão. Depois teste abort e lifecycle.
Erros comuns
- Bucket público: objetos ficam expostos.
- Key com nome do usuário: colisão e path confuso.
- Credencial estática: rotação e vazamento ficam difíceis.
- Confiar no Content-Type: arquivo malicioso passa.
- URL longa: credencial temporária vaza.
- Multipart sem abort: armazenamento cresce.
- Sem confirmação: banco aponta para objeto inexistente.
Boas práticas
- Use identidade de workload.
- Mantenha bucket privado.
- Gere keys no servidor.
- Use streams.
- Limite tamanho e tipo.
- Prefira URL curta.
- Valide HeadObject.
- Use quarentena.
- Aborte multipart incompleto.
- Audite downloads e exclusões.
Conclusão
Fazer uploads para S3 no Node.js permite separar arquivos do servidor da aplicação e escalar armazenamento. O desenho seguro começa com keys controladas, bucket privado e identidade de workload.
URLs pré-assinadas, multipart, checksums e lifecycle completam o fluxo. Com validação, quarentena, autorização por tenant e limpeza de órfãos, o object storage recebe arquivos sem transformar uploads em uma fonte de vazamento, execução maliciosa ou custo invisível.




