Path Traversal via res.sendFile() in Express

High Risk Path Traversal
javascriptexpresspath-traversalsendfilefile-access

What it is

Path traversal vulnerabilities occur when user-controlled paths are passed to res.sendFile() without proper validation. Even though res.sendFile() has some built-in protections, attackers can bypass them using absolute paths or by exploiting misconfigurations, accessing files outside the intended directory and potentially exposing sensitive application files, configuration, or system files.

const express = require('express');
const app = express();

// VULNERABLE: user input directly in sendFile
app.get('/download', (req, res) => {
    const filename = req.query.file;
    
    // DANGEROUS: no root option, allows absolute paths
    res.sendFile(filename, (err) => {
        if (err) {
            res.status(404).send('File not found');
        }
    });
});

app.get('/file', (req, res) => {
    const file = req.params.file;
    const baseDir = '/app/uploads/';
    
    // VULNERABLE: manual concatenation without validation
    res.sendFile(baseDir + file);
});

// Attack: file=/etc/passwd (absolute path)
// Attack: file=../../../etc/passwd (traversal)
const express = require('express');
const path = require('path');
const app = express();

const ALLOWED_FILES = [
    'document.pdf',
    'image.jpg',
    'report.xlsx'
];

// SECURE: use root option and validate filename
app.get('/download', (req, res) => {
    const filename = req.query.file;
    
    // Validate against allowlist
    if (!ALLOWED_FILES.includes(filename)) {
        return res.status(400).send('Invalid file');
    }
    
    // SAFE: root option restricts to base directory
    res.sendFile(filename, {
        root: path.join(__dirname, 'uploads'),
        dotfiles: 'deny'  // Prevent access to hidden files
    }, (err) => {
        if (err) {
            res.status(404).send('File not found');
        }
    });
});

app.get('/file/:file', (req, res) => {
    const file = req.params.file;
    
    // Validate filename doesn't contain path separators
    if (file.includes('/') || file.includes('\\') || file.includes('..')) {
        return res.status(400).send('Invalid filename');
    }
    
    // SECURE: root option enforces base directory
    res.sendFile(file, {
        root: '/app/uploads',
        dotfiles: 'deny'
    });
});

💡 Why This Fix Works

The vulnerable code passes user input directly to res.sendFile() without the root option or validation, allowing absolute paths and path traversal. The secure version validates filenames against an allowlist and uses the root option to restrict file access to a specific directory.

Why it happens

Passing user-controlled filenames directly to res.sendFile().

Root causes

User Input in res.sendFile()

Passing user-controlled filenames directly to res.sendFile().

Missing root Option

Not specifying the root option to restrict file access to a specific directory.

Absolute Path Handling

Allowing absolute paths which bypass directory restrictions.

Fixes

1

Always Use root Option

Specify root option in res.sendFile() to restrict access to a base directory.

2

Validate Filename Format

Reject filenames containing path separators or traversal sequences.

3

Use Filename Allowlists

Validate filenames against an allowlist of permitted files.

Detect This Vulnerability in Your Code

Sourcery automatically identifies path traversal via res.sendfile() in express and many other security issues in your codebase.