When a filename or path from a request is joined onto a directory, an attacker can send ../ sequences to escape that directory and read or write files elsewhere on the server. The fix is to resolve the final path and confirm it still sits inside the directory you intended before you touch it.
Why it's a problem
A path like ../../../../etc/passwd or an absolute path can pull files far outside your intended folder, exposing configuration, other users' uploads, or credentials. The same flaw on a write or delete operation lets an attacker overwrite files they should never reach.
The pattern
// user controls the filename const file = path.join(uploadDir, req.query.name); res.sendFile(file);
The fix
const requested = path.resolve(uploadDir, req.query.name);
// confirm the resolved path is still inside uploadDir
if (!requested.startsWith(path.resolve(uploadDir) + path.sep)) {
return res.sendStatus(400);
}
res.sendFile(requested);Why AI tools write this
Joining a directory and a request parameter is the obvious way to express 'serve the file the user asked for,' and it works perfectly for normal filenames. The traversal only shows up when someone deliberately sends ../ sequences, which a happy-path test never does.
The quick fix
- Resolve the final path and confirm it starts inside your intended directory.
- Strip path separators or use path.basename when you only expect a bare filename.
- Prefer an allowlist of known files or ids over accepting arbitrary paths.
Common questions
Is path.join alone enough to prevent traversal?
No. path.join normalizes slashes but does not prevent ../ sequences from escaping the base directory. You must resolve the final path and confirm it is still inside the intended directory.
How do I handle this for a file download endpoint?
Resolve the full path from the base directory, confirm the result starts with the base directory plus a separator, and only then open the file. Reject anything that resolves outside.
What if I only accept a bare filename with no path?
Use path.basename to strip any directory component before joining. An input like ../../../../etc/passwd becomes passwd, which is then safe to join with your base directory.