An application is supposed to serve files from one folder. A user asks for a file called "../../etc/passwd" and gets something from somewhere else entirely. That is path traversal, and CWE-22 is its official name.
MITRE's wording: the product uses external input to construct a pathname meant to identify a file or directory under a restricted parent directory, but does not properly neutralize special elements in the pathname, so it can resolve to a location outside the restricted directory.
Anything that turns user supplied text into a file path can have it. Download endpoints, upload handlers, template loaders, archive extractors, log viewers, plugin loaders and model registries all do this, and the record lists AI and ML systems among the affected platforms.
How it happens
Many file operations are meant to take place inside one directory. Attackers use special elements, the ".." and "/" separators, to escape it. MITRE singles out "../" as one of the most common, which most modern operating systems interpret as the parent of the current location. That is relative path traversal.
It also covers absolute paths. If the input is "/usr/local/bin" and the code uses it as given, the attacker has chosen the location outright. That is absolute path traversal.
Here is the vulnerable pattern in Python:
from flask import Flask, request, send_file
import os
app = Flask(__name__)
BASE = "/srv/app/uploads"
@app.route("/download")
def download():
name = request.args["name"]
return send_file(os.path.join(BASE, name)) # name can contain ../
os.path.join does not protect you. If name is "../../etc/passwd", the joined path climbs out. If name starts with a slash, join discards the base entirely, which is the absolute case.
The same pattern in Java:
File f = new File("/srv/app/uploads", request.getParameter("name"));
return Files.readAllBytes(f.toPath());
And in Node:
const p = path.join(BASE, req.query.name);
res.sendFile(p);
The language changes. The mistake does not: a path built from input, used without checking where it lands.
What an attacker gains
MITRE lists four consequences.
- Executing unauthorized code or commands: the attacker may create or overwrite files used to run code, such as programs or libraries.
- Modifying files or directories: overwriting or creating critical files. If the file belongs to a security mechanism, that mechanism can be bypassed. The record's example is appending a new account to a password file.
- Reading files or directories: exposing sensitive data, or reading a password file to learn credentials.
- Denial of service: overwriting, deleting or corrupting critical files, which can stop the product from working at all.
Observed cases in the record, several in AI tooling, show how current this is:
- CVE-2024-37032: an LLM management tool did not validate the format of a digest value from an untrusted model registry, enabling relative path traversal.
- CVE-2024-4315: a chain in an LLM text generation API where the Windows folder separator was left out of a denylist, defeating a local file inclusion defense.
- CVE-2024-0520: a dataset management product allowed both relative and absolute traversal to overwrite files through the Content-Disposition header.
- CVE-2022-45918: a chain in a learning management tool where a debugger used external input to find earlier session logs without properly validating the path.
Two lessons from that list. The input does not have to be a query parameter. A digest value and an HTTP header both worked. And a denylist that forgets one separator is as good as no denylist.
How to find it
MITRE lists automated static analysis, which can find places where path traversal exists. It adds a caveat: tuning may be needed to remove or lower the priority of cases only exploitable by an administrator or another privileged user, who could already reach those files.
Manual white box review can give enough coverage with few false positives if every file access operation can be assessed within the time available. Bytecode and binary analysis offer partial coverage for compiled code.
By hand, search for file open, read, write, send and extract calls, and trace where each path argument comes from. Anything touched by request data, headers, archive entry names or registry fields deserves a look. Archive extraction is a favorite hiding place, since entry names are attacker controlled.
Try it yourself
Run a code scan and look at how a static check reports a path built from request data.
Click through it at your own pace, or open it full screen.
How to fix it
The record lists no mitigations, so these are the standard defenses that follow from the weakness.
- Avoid user supplied paths. Take an ID, look up the real file name in a database or map, and build the path yourself.
- If you must accept a name, resolve it to its canonical form and then confirm it sits inside the allowed base.
- Compare path components, not raw text. A plain prefix test lets "/srv/app/uploads2" pass as inside "/srv/app/uploads".
- Resolve links too, since a symbolic link inside the folder can point outside.
- Handle both separators on every platform.
- Do not rely on stripping "../" from the input. Removal can be defeated by nested sequences that collapse back into the original.
- Run with least privilege. A service that can only read its content folder limits the damage of a miss.
- For archives, check each entry's resolved destination before writing it.
The fixed Python version:
import os
from flask import Flask, request, send_file, abort
app = Flask(__name__)
BASE = os.path.realpath("/srv/app/uploads")
@app.route("/download")
def download():
full = os.path.realpath(os.path.join(BASE, request.args["name"]))
if os.path.commonpath([BASE, full]) != BASE:
abort(403)
return send_file(full)
realpath resolves dots and links. commonpath compares whole components. A request that escapes the folder gets a 403.
Do not forget the supply chain side. The record's AI tooling cases are flaws in packages that other projects install. If your project depends on one of them, the weakness is in your code too. A dependency scan finds known issues, and reachability analysis tells you whether your code calls the affected function. The free in-browser scan at gold.safeguard.sh/scan needs no login, keeps the file on your machine, and covers up to 100 dependencies.
Quick check
Does os.path.join keep paths inside the base folder?
No. It joins strings. A name containing "../" climbs out, and a name starting with a slash replaces the base completely.
Why is stripping "../" from input unreliable?
Single pass removal can leave behind characters that rejoin into "../". Resolving the final path and checking it against the base avoids the problem.
Is path traversal only about reading files?
No. MITRE lists writing and overwriting files, which can lead to code execution, as well as deletion and denial of service.