Path Traversal acontece quando uma aplicação usa entrada do usuário para montar caminhos e permite sair do diretório autorizado. Sequências como ../, caminhos absolutos, separadores alternativos e codificações podem expor arquivos, sobrescrever dados ou atingir configurações sensíveis.
Exemplo vulnerável
app.get('/files/:name', async (req, res) => {
const filePath = path.join('/app/uploads', req.params.name);
res.sendFile(filePath);
});Um nome como ../../etc/passwd pode escapar, dependendo de normalização e uso posterior.
Use identificadores, não caminhos
A solução mais robusta é mapear um ID para um caminho armazenado pelo servidor:
const file = await files.findById(req.params.id);
if (!file || file.tenantId !== req.auth.tenantId) {
throw new NotFoundError();
}
return streamPrivateFile(file.storageKey, res);O cliente nunca escolhe diretório ou nome físico.
Verificação de containment
const ROOT = path.resolve('/app/public-files');
function resolveInsideRoot(input) {
const candidate = path.resolve(ROOT, input);
const relative = path.relative(ROOT, candidate);
if (relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error('Caminho fora da raiz');
}
return candidate;
}Compare caminhos normalizados. Uma simples verificação com startsWith(ROOT) pode aceitar diretórios com prefixos semelhantes.
Allowlist de nomes
Quando o nome precisa vir do usuário, aceite um formato restrito:
if (!/^[a-zA-Z0-9_-]{1,80}\.pdf$/.test(filename)) {
throw new ValidationError('Nome inválido');
}Allowlist não substitui containment, mas reduz possibilidades.
Separadores de plataforma
Windows aceita barras invertidas e caminhos com drive. Teste em todas as plataformas de produção. Não substitua apenas ../ em string.
Codificação
Frameworks podem decodificar parâmetros automaticamente. Ataques usam percent-encoding simples ou duplo. Trabalhe com o valor final decodificado uma vez e não aplique múltiplas decodificações manuais.
Null bytes
Ambientes modernos tratam null bytes de forma mais segura, mas valide entrada e não dependa de extensão adicionada ao final para proteção.
Symlinks
Mesmo um caminho dentro da raiz pode apontar por symlink para fora. Em diretórios controlados por usuários, evite seguir symlinks, use flags seguras quando disponíveis e verifique o caminho real:
const realRoot = await fs.realpath(ROOT);
const realFile = await fs.realpath(candidate);
const relative = path.relative(realRoot, realFile);
if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error();Existe risco de race condition entre verificação e abertura. Prefira armazenamento onde usuários não conseguem criar links.
TOCTOU
Time-of-check to time-of-use acontece quando o arquivo muda entre validar e abrir. Reduza a janela, use descritores, diretórios protegidos e APIs que operem sobre handles.
Uploads
Nunca use originalname como caminho. Gere UUID e mantenha extensão apenas se validada por conteúdo.
Extração de ZIP
Zip Slip é path traversal dentro de arquivos compactados:
for (const entry of archive.entries) {
const output = resolveInsideRoot(entry.name);
await extractEntry(entry, output);
}Rejeite caminhos absolutos, .., links e tamanho descompactado excessivo.
sendFile
Alguns frameworks oferecem opção root:
res.sendFile(filename, { root: ROOT });Mesmo assim, use nomes validados e revise o contrato da versão do framework.
Arquivos de template
Não permita que o usuário escolha um template arbitrário. Mapeie valores conhecidos:
const templates = {
invoice: 'invoice.html',
receipt: 'receipt.html',
};
const template = templates[req.query.type];
if (!template) throw new ValidationError();Logs
Logs também podem sofrer path injection se o nome do arquivo vem do cliente. Use destino fixo e campos estruturados, não um arquivo por usuário.
Static files
Configure diretórios públicos explicitamente. Não sirva a raiz do projeto, uploads privados, arquivos .env, source maps ou repositório Git.
Permissões do processo
Execute como usuário sem privilégios. Mesmo que traversal ocorra, o processo não deve ler chaves, secrets ou arquivos do sistema.
Containers
Use filesystem somente leitura quando possível, monte volumes mínimos e não inclua segredos na imagem. Container não elimina traversal.
Respostas
Retorne 404 ou 400 sem revelar caminho real. Não devolva mensagens como “arquivo /etc/passwd não permitido”.
Testes
Teste:
../e variantes;- barras invertidas;
- caminho absoluto;
- percent-encoding;
- dupla codificação;
- symlink;
- nome muito longo;
- prefixos parecidos;
- ZIP com entradas maliciosas.
Erros comuns
- remover ../ por replace;
- confiar em path.join sozinho;
- usar startsWith simples;
- ignorar Windows;
- usar nome original de upload;
- seguir symlinks;
- servir a raiz do projeto;
- processo com permissões amplas;
- expor caminho nos erros.
Fluxo recomendado
- use IDs opacos;
- mapeie no servidor;
- normalize com resolve;
- verifique relative;
- aplique allowlist;
- controle symlinks;
- restrinja permissões;
- teste codificações;
- monitore tentativas.
Combine a proteção com Upload Seguro de Arquivos, Multipart, Tratamento de Erros e Audit Logs.
Consulte o guia de Path Traversal da OWASP e a documentação oficial de path.



