Securityhigh

Path Traversal

Path traversal (directory traversal) attacks use sequences like ../../../etc/passwd in user-controlled file paths to access files outside the intended directory. Node.js's fs module will happily follow these paths.

Memory anchor

Path traversal = someone asking for 'Room 3, go back, back, back, into the vault.' The ../../../ is climbing stairs backwards out of the allowed floor. The fix: resolve the full address, then check 'are you still in the building?' (startsWith). path.resolve is just GPS—it tells you WHERE you'd end up, not whether you're ALLOWED there.

Expected depth

Vulnerable code: `fs.readFile('./uploads/' + req.params.filename, ...)`. An attacker requests /files/../../../etc/passwd. Fix: use path.resolve() to get the absolute path, then verify it starts with the allowed base directory: `const safe = path.resolve('/uploads', filename); if (!safe.startsWith('/uploads/')) return 403;`

Deep — senior internals

path.join() does not protect against traversal—it resolves ../ sequences but doesn't validate against a base directory. path.resolve() resolves to an absolute path, and combining it with a startsWith check is the correct pattern. Additional considerations: (1) URL encoding (%2F, %2e%2e) can bypass naive string checks—always decode before checking or use path.resolve. (2) Null byte injection (filename%00.jpg) can truncate filenames in some native code. (3) Case sensitivity: Windows paths are case-insensitive while Linux paths are not—check for both if deploying to mixed environments. (4) Use a library like resolve-path or serve-static (which handles this internally) rather than rolling your own.

🎤Interview-ready answer

Path traversal allows file system access outside intended directories via ../ sequences. The fix: resolve the user-provided path with path.resolve('/safe/base', userInput), then assert the result starts with '/safe/base/'. Never use path.join() alone as it resolves ../ but doesn't enforce a base constraint. URL-decode inputs before validation to prevent encoding bypass.

Common trap

path.resolve('/base', '../etc/passwd') returns '/etc/passwd'—it resolves to an absolute path correctly, but a naive check for '/base' prefix passes because '/etc/passwd'.startsWith('/base') is false, which is actually correct behavior. The trap is thinking path.resolve prevents traversal rather than understanding it only resolves the path; the startsWith check is the actual security control.