Safeguard
Vulnerability Guides

Path Traversal Vulnerability Prevention, Explained

One unvalidated filename and `../../../etc/passwd` reads files you never meant to expose — or worse, executes them. Here's how path traversal works and how to build file access that can't be tricked.

Daniel Osei
Security Researcher
Updated 5 min read

Serving a file by name looks like the most basic operation in web development. It is also one of the most consistently dangerous, because filesystems interpret paths in ways your intended logic never accounted for. Give an attacker any influence over a file path and ../ becomes a key to the rest of the disk.

What is path traversal?

Path traversal (also called directory traversal, CWE-22) is a vulnerability where an application uses unsanitized user input to build a filesystem path, allowing an attacker to use sequences like ../ to escape the intended directory and read, write, or execute files elsewhere on the system. Depending on what the application does with the resolved path, impact ranges from leaking configuration and credentials to overwriting files and achieving remote code execution.

How the attack works

The application takes a filename, joins it to a base directory, and opens the result:

/var/www/uploads/ + user_input

If user_input is ../../../../etc/passwd, the joined path resolves outside uploads to a sensitive system file. Attackers layer on evasions when naive filters get in the way:

  • Encoding: %2e%2e%2f (URL-encoded ../), double encoding %252e%252e%252f, and overlong UTF-8 sequences.
  • Mixed separators: ..\ on Windows, which also honors backslashes and drive-relative paths.
  • Absolute paths: supplying /etc/passwd directly when the base is joined naively.
  • Null bytes and truncation in older runtimes to cut off an appended extension.

This is not a museum piece. CVE-2021-41773 was a path traversal in Apache HTTP Server 2.4.49 that, with certain configurations, allowed reading files outside the document root and even remote code execution; the initial fix in 2.4.50 was incomplete and had to be re-fixed as CVE-2021-42013. Both were exploited in the wild within days of disclosure.

Vulnerable vs. fixed

A download endpoint that joins user input to a directory:

# VULNERABLE — user input joined and opened with no containment check
import os
from flask import request, send_file

BASE = "/var/www/uploads"

@app.get("/download")
def download():
    name = request.args["file"]            # e.g. ../../../../etc/passwd
    return send_file(os.path.join(BASE, name))

The fix resolves the path to its real, canonical form and verifies it is still inside the base directory before touching the file:

# FIXED — canonicalize, then confirm containment inside the base directory
import os
from flask import request, send_file, abort

BASE = os.path.realpath("/var/www/uploads")

@app.get("/download")
def download():
    name = request.args["file"]
    target = os.path.realpath(os.path.join(BASE, name))
    # commonpath raises / mismatches if target escaped BASE via ../, symlinks, etc.
    if os.path.commonpath([BASE, target]) != BASE:
        abort(403)
    return send_file(target)

The crucial move is realpath (canonicalization), which resolves .., symlinks, and redundant separators before the containment check. Comparing the canonical target against the canonical base defeats encoding tricks and symlink escapes that string-level filtering misses. Even stronger: do not accept file paths at all — map an opaque ID to a known-safe filename server-side.

Prevention checklist

  • Canonicalize before you validate. Resolve the full real path (realpath, Path.GetFullPath, file.getCanonicalPath) and confirm it stays within the intended base directory. A path traversal vulnerability fix in Java specifically means calling File.getCanonicalPath() (or Path.normalize() on an NIO.2 Path) on the resolved target and checking with startsWith() against the canonicalized base directory before any FileInputStream or Files call touches it — the same containment check as the Python example above, just spelled with Java's APIs.
  • Prefer indirection over paths. Let users request an ID; look up the actual filename from a server-controlled map. The safest input is one that never becomes a path.
  • Use an allow-list of filenames or extensions rather than trying to blacklist ../.
  • Decode fully, then check. Account for URL encoding, double encoding, and OS-specific separators before validation.
  • Drop filesystem privileges. Run the service as a low-privilege user, and consider chroot, containers, or a jailed storage mount so even an escape reaches little.
  • Never place user-controlled files in executable or web-served paths where a traversal-write could become code execution.
  • Keep servers and frameworks patched — the Apache 2.4.49/2.4.50 saga shows how quickly these get exploited.

Zip Slip: traversal without a request parameter

Not every path traversal comes from a URL parameter. "Zip Slip" is a widespread variant where an archive (zip, tar, jar) contains an entry whose name includes ../, and a naive extraction routine writes that entry outside the intended directory — potentially overwriting a binary, a config file, or a web-served script to gain code execution. It affected extraction code across many ecosystems and is easy to reintroduce whenever someone hand-rolls an unzip loop. The fix follows the same principle as request-based traversal: after resolving each entry's destination path, confirm it is still contained within the target directory before writing, and reject any entry that escapes. Treat every archive from an untrusted source the way you treat a raw file path — as adversarial input that must be canonicalized and contained.

How Safeguard helps

Path traversal is both a code-level flaw and, when it lands in a web server or framework, a dependency-level one. Griffin AI code review follows user input into file APIs (open, send_file, File, fs.readFile) and flags paths built without canonicalization or a containment check — the exact gap in the vulnerable example above. Safeguard's DAST engine attacks file-serving endpoints with encoded and OS-specific traversal payloads to confirm real, exploitable escapes rather than theoretical ones. Safeguard's software composition analysis tracks path-traversal advisories in your web servers, frameworks, and archive-extraction libraries (a frequent source of "zip slip" traversal). When the fix is a version bump or a containment guard, Safeguard's auto-fix drafts the pull request, and the Safeguard CLI runs the same checks in CI and on developer machines.

See how a modern platform compares to legacy scanners in Safeguard vs Checkmarx.

Get started free at app.safeguard.sh/register, or read the file-security guides at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.