← ./articles

Safe Path Containment on Windows in Node.js: Avoid startsWith Checks

Checking whether a file path stays inside a root directory looks simple:

filePath.startsWith(rootPath)

On Windows, that is not enough.

Windows path rules include drive-relative paths, UNC paths, device paths, case-insensitive drives, and .. traversal. A string prefix check can approve the wrong path or reject a valid one.

The safer pattern

Use path.resolve() and path.relative():

const path = require("path");

function isSafeInRoot(filePath, rootPath) {
  if (typeof filePath !== "string" || typeof rootPath !== "string") {
    return false;
  }

  if (/^[a-zA-Z]:[^/\\]/.test(filePath)) {
    return false; // drive-relative, e.g. C:foo
  }

  if (/^\\\\/.test(filePath) || /^\/\//.test(filePath)) {
    return false; // UNC or device-style input
  }

  const resolvedFile = path.resolve(filePath);
  const resolvedRoot = path.resolve(rootPath);
  const relative = path.relative(resolvedRoot, resolvedFile);

  return relative !== "" &&
    !relative.startsWith("..") &&
    !path.isAbsolute(relative);
}

Depending on your use case, you may want relative === "" to be allowed. For file upload checks, rejecting the root itself is often simpler.

Why startsWith fails

Consider these cases:

<root>\safe
<root>\safe-old

The second path starts with the same string but is not inside the first directory.

Now add Windows-specific forms:

<drive-relative>foo
\\server\share\file.txt
\\?\<root>\safe\file.txt
<root>\safe\..\secret.txt

You need canonical path comparison, not raw string comparison.

Use it at the boundary

Validate paths as close as possible to input:

  • HTTP upload target
  • CLI argument
  • IPC command
  • file picker result
  • plugin API call

Do not wait until a later fallback branch. If the input is outside the allowed root, reject it before reading or writing.

Summary

For Windows path containment, avoid startsWith.

Resolve both paths, compute the relative path from root to target, and reject anything absolute or starting with ... Also reject drive-relative and UNC/device-style input before resolution.

References