FlawOpen

flawopen.com/Path Traversal/Node path.join()

Does Node.js path.join() prevent path traversal?

Reference page
Short answer

No. path.join() normalizes path segments, but it does not stop .. from escaping an intended directory. Resolve the candidate against the base directory, then verify that the relative path stays inside the base. This lexical check does not by itself contain filesystem symlinks.

STILL VULNERABLE
const filePath = path.join(
  BASE_DIR, userFilename
);
// userFilename = "../../etc/passwd"
// still escapes BASE_DIR
FIXED — lexical containment check
const base = path.resolve(BASE_DIR);
const resolved = path.resolve(base, userFilename);
const relative = path.relative(base, resolved);

if (
  relative === '' ||
  relative === '..' ||
  relative.startsWith('..' + path.sep) ||
  path.isAbsolute(relative)
) {
  throw new Error("Invalid path");
}

Why path.join() alone does not help

path.join() combines path segments with the platform-specific separator and normalizes the result. Normalization does not reject .., which is valid in many paths. Joining ../../etc/passwd to a base can still produce a path outside that base; join() is not a containment check.

FAQ

Does path.normalize() help instead?

path.normalize() resolves .. and . segments in the string, but it does not verify containment within an allowed base. Compare path.relative(base, resolved) and reject an empty result when a file is required, .., a value starting with .. plus path.sep, or an absolute relative result.

Does this check stop symlink escapes?

No. path.resolve() and path.relative() compare path strings; they do not follow filesystem symlinks. If an attacker can create or replace symlinks under the base, validate the resolved filesystem target and use filesystem access patterns that prevent a check/use race. Restrict write access to the tree when possible.

References

See also Path Traversal (CWE-22): How It Works and How to Prevent It in Every Language — flawopen.com PHP include() and User-Controlled Paths Python os.path.join() Path Traversal Fix What Is a Zip Slip Vulnerability?