Safeguard
Application Security

Preventing path traversal (directory traversal) attacks

Path traversal lets attackers read or write files outside a web app's directory using ../ sequences. Here's how it works and how to stop it.

Bob
Application Security Engineer
7 min read

Path traversal — also called directory traversal — is a vulnerability class where an attacker manipulates file-path input to reach files outside the directory a web application intended to expose. A request like GET /download?file=../../../../etc/passwd or an upload path containing ..\..\..\Windows\System32\ can let an attacker read source code, configuration files, and credentials, or write files into locations that grant remote code execution. It is tracked as CWE-22 and sits inside OWASP's Broken Access Control category, which OWASP's 2021 Top 10 data set found present in 94% of tested applications with an average incidence rate of 3.81% — the highest of any category that year. Path traversal is not exotic or rare; it shows up in file upload endpoints, static file servers, archive extraction routines, template engines, and log viewers across every language and framework in production today.

What is a path traversal attack, exactly?

A path traversal attack is any technique that uses relative path sequences (../), absolute paths, or encoded variants of both to make an application read, write, or execute a file outside its intended directory boundary. The classic payload is ../ repeated enough times to climb out of a web root — for example, ../../../../etc/passwd on Linux or ..\..\..\..\windows\win.ini on Windows. Attackers also use URL-encoded traversal (%2e%2e%2f), double encoding (%252e%252e%252f), null-byte injection (file.txt%00.jpg, effective against older PHP versions), and absolute-path injection (/etc/passwd passed directly when an app naively concatenates a "base directory" with user input). The impact ranges from information disclosure — reading /etc/passwd, .env files, or SSH keys — to full remote code execution when the traversal lets an attacker overwrite a configuration file, cron job, or web-accessible script.

How does a path traversal bug actually get into code?

A path traversal bug gets introduced whenever a file path is built by concatenating a fixed base directory with untrusted input and the result is used without validating that it still resolves inside that base directory. A common vulnerable pattern in Node.js looks like this:

app.get('/files', (req, res) => {
  const filePath = path.join(__dirname, 'uploads', req.query.name);
  res.sendFile(filePath); // req.query.name = "../../../etc/passwd" escapes uploads/
});

path.join normalizes the string but does nothing to stop .. segments from walking above uploads/. The same class of bug appears in Java (new File(baseDir, userInput)), Python (os.path.join(base_dir, user_input)), and PHP (include($_GET['page'] . '.php') — the classic local file inclusion variant). Archive extraction is a particularly persistent variant known as "Zip Slip," disclosed by Snyk's security team in 2018: a malicious ZIP or TAR entry named ../../../etc/cron.d/evil writes outside the extraction directory when the unzip code trusts the entry's stored filename. Trellix researchers reported in December 2022 that the 15-year-old Python tarfile.extractall() traversal bug (CVE-2007-4559) was still present and exploitable in roughly 350,000 open-source repositories they scanned, because the fix required an opt-in filter parameter that most code never adopted.

What real-world CVEs show how damaging this class of bug is?

Real-world CVEs show that path traversal routinely escalates from file disclosure to full remote code execution within days of disclosure. CVE-2021-41773, disclosed in Apache HTTP Server 2.4.49 in October 2021, let attackers map URLs to files outside the configured document root and, when CGI scripts were enabled, achieve RCE; it carried a CVSS score of 9.8 and mass exploitation began within 48 hours of the advisory. The initial patch was incomplete, requiring a second CVE (CVE-2021-42013) for Apache 2.4.50. CVE-2019-3396, a path traversal in the Atlassian Confluence Widget Connector macro, let attackers upload files to arbitrary locations on the server and was actively exploited by state-linked threat actors to deploy web shells within weeks of disclosure. More recently, CVE-2024-4577, a PHP-CGI argument-injection flaw affecting PHP on Windows in XAMPP configurations (patched June 2024), showed how locale-related path handling bugs continue to surface in widely deployed stacks. Each of these cases shares the same root cause: user-controlled input reached a filesystem or path-resolution API without being constrained to an allowlisted, canonicalized location.

How do you actually prevent path traversal in your application?

You prevent path traversal by validating that every resolved file path stays inside an intended base directory, and by avoiding user input in filesystem paths wherever possible. The most reliable pattern combines several controls:

  1. Canonicalize, then verify containment. Resolve the final path with path.resolve() / realpath() / Path.toRealPath() and confirm the result still starts with the intended base directory before touching the filesystem — checking the raw string for .. is not sufficient, because encoded and symlink-based bypasses exist.
  2. Prefer identifiers over filenames. Map user requests to a database ID or a generated UUID rather than accepting a filename or path fragment directly; this eliminates the traversal surface entirely for most download/upload endpoints.
  3. Allowlist characters and extensions. Reject any input containing /, \, null bytes, or .. outright for filename fields, and allowlist expected file extensions on upload.
  4. Run extraction and file operations with least privilege. Use archive libraries that reject entries resolving outside the target directory (Python 3.12+ ships tarfile with filter='data' as the safer default; Java's java.util.zip still requires manual containment checks), and run the service account with no write access outside its designated storage path.
  5. Isolate the filesystem boundary. Use chroot jails, containers with read-only root filesystems, or cloud storage (S3, GCS) with per-object access checks instead of a raw local filesystem path wherever the architecture allows it.

A corrected version of the Node.js example above looks like this:

const uploadsDir = path.resolve(__dirname, 'uploads');
const requested = path.resolve(uploadsDir, req.query.name);
if (!requested.startsWith(uploadsDir + path.sep)) {
  return res.status(400).send('Invalid file path');
}
res.sendFile(requested);

Which tools catch path traversal before it reaches production?

Static analysis (SAST) and software composition analysis (SCA) tools catch path traversal before production by flagging user-input-to-filesystem-sink data flows and by identifying dependencies with known CWE-22 CVEs before they ship. SAST rules that track taint from HTTP request objects to File, fs.readFile, open(), or archive-extraction calls will catch the vulnerable pattern shown above at the pull-request stage, well before a WAF or runtime monitoring tool would see an exploit attempt. SCA and SBOM-based scanning catch the dependency side of the problem — for example, an application bundling an outdated archive library still vulnerable to Zip Slip, or a transitive dependency carrying an unpatched CVE-2007-4559-style flaw. Because path traversal frequently depends on how a specific framework's path-joining function behaves (Node's path.join versus Python's os.path.join versus Java's File constructor all normalize differently), generic pattern-matching tools produce a high volume of false positives; the practical difference between a useful finding and noise is whether the tool can confirm the vulnerable code path is actually reachable from an externally facing route.

How Safeguard Helps

Safeguard reduces path traversal risk across the software supply chain by combining reachability analysis with dependency and code-level scanning, so security teams see which CWE-22 findings sit on a path an attacker can actually reach from an exposed endpoint rather than triaging every theoretical match. Griffin AI reviews flagged file-handling and archive-extraction code paths, explains the specific taint flow from user input to filesystem sink in plain language, and drafts the fix. Safeguard's SBOM generation and ingest pipeline tracks which third-party libraries (archive utilities, template engines, upload handlers) carry known path traversal CVEs across your entire dependency tree, including transitive dependencies most teams never audit manually. When a fix is available, Safeguard opens an auto-fix pull request with the corrected containment check or dependency bump already applied, so the remediation ships in the same review cycle the finding was surfaced in rather than sitting in a backlog.

Never miss an update

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