Back to Blog
high SEVERITY6 min read

How Path Traversal happens in Python Flask apps and how to fix it

A path traversal vulnerability in `WAVE2SCORE/app.py` allowed attackers to supply a crafted file path that escaped the application's intended working directory, potentially enabling access to arbitrary files on the server. The fix resolves the absolute path and validates it against the expected `WORK_DIR` boundary before any processing occurs. This kind of boundary check is a critical safeguard in any application that processes user-supplied file paths.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

This is a path traversal vulnerability (CWE-22) in a Python application (`WAVE2SCORE/app.py`) where the `run_job()` function accepted a user-controlled `input_path` without resolving or validating it against the application's work directory. An attacker could supply a path like `../../etc/passwd` to escape the intended directory. The fix calls `Path(job["input_path"]).resolve()` to canonicalize the path and then checks that the resolved path starts with `WORK_DIR.resolve()`, rejecting any path that falls outside the permitted boundary.

Vulnerability at a Glance

cweCWE-22
fixResolve the path with `.resolve()` and assert it starts with `WORK_DIR.resolve()` before use
riskAttacker-controlled file path can escape the work directory, enabling unauthorized file access or processing
languagePython
root cause`input_path` was constructed directly from job data without resolving symlinks or validating the canonical path against `WORK_DIR`
vulnerabilityPath Traversal

Introduction

The WAVE2SCORE/app.py file is the heart of a wave-file scoring pipeline. It accepts job metadata, locates an uploaded audio file, and passes it to a processing script. That workflow sounds straightforward — but a flaw in the run_job() function meant that the input_path variable was never checked to confirm it actually lived inside the application's designated work directory.

The result: an attacker who could influence the input_path value stored in a job record could point the application at any file on the server — not just the uploaded .wav files it was designed to handle.


The Vulnerability Explained

What the code looked like before the fix

# BEFORE — vulnerable code (around line 58)
job_dir = Path(job["job_dir"])
input_path = Path(job["input_path"])   # ← no resolution, no boundary check
out_base = str(job_dir / "result")

# ... later, input_path is passed to subprocess

job["input_path"] comes from stored job data that originates with user input (an uploaded file). The application wraps it in a Path object, but Path(some_string) does not resolve .. components or follow symlinks — it just stores the string as-is.

Why this is dangerous

Consider what happens if input_path is set to something like:

../../etc/passwd

or an absolute path:

/var/secrets/api_keys.env

Because the code never calls .resolve() and never checks whether the resulting path falls inside WORK_DIR, both of those values pass through unchallenged. The downstream subprocess.run call (which invokes main.py with this path as an argument) will happily attempt to process whatever file the attacker named.

Concrete attack scenario

  1. An attacker submits a legitimate job to establish a valid session.
  2. They then manipulate the input_path field in the job record — either through a direct API call, a race condition, or a second-order injection — to contain ../../etc/shadow.
  3. run_job() constructs input_path = Path("../../etc/shadow") without complaint.
  4. The path is passed to main.py via subprocess.run. Depending on what main.py does with the file, the attacker may be able to read its contents, trigger error messages that leak data, or cause unexpected behavior in the scoring pipeline.

Real-world impact

This application appears to be publicly accessible. A successful exploit could expose sensitive server files, disrupt the scoring service, or serve as a stepping stone for deeper compromise.


The Fix

The patch makes two targeted changes inside run_job():

Before

input_path = Path(job["input_path"])

After

input_path = Path(job["input_path"]).resolve()

if not str(input_path).startswith(str(WORK_DIR.resolve())):
    raise RuntimeError("Invalid input path: outside of work directory")

Change 1 — .resolve()

Path.resolve() returns the absolute, canonical path. It expands every .. component and follows symlinks. After this call, ../../etc/shadow becomes /etc/shadow — its true identity is revealed before any comparison is made.

Change 2 — boundary check

The resolved path is then compared against WORK_DIR.resolve() (also canonicalized for the same reason). If the input path does not start with the work directory prefix, a RuntimeError is raised immediately and the job is aborted. No file outside WORK_DIR can ever reach the subprocess call.

This is a minimal, surgical fix: two lines added, zero functionality removed.

Note on the startswith check: For production hardening, consider using input_path.is_relative_to(WORK_DIR.resolve()) (Python ≥ 3.9) instead of the string startswith comparison. The is_relative_to method is path-aware and avoids edge cases where one directory name is a prefix of another (e.g., /work vs /work-extra).


Key Takeaways

  • Path(user_input) is not safe — always follow it with .resolve() before any comparison or filesystem operation in WAVE2SCORE/app.py and similar job-runner code.
  • The input_path variable in run_job() was the specific taint source — any job field that feeds a filesystem call needs the same treatment.
  • A two-line fix closed the hole — resolving the path and checking it against WORK_DIR is all it took; no architectural changes were needed.
  • String startswith on paths has edge cases — prefer Path.is_relative_to() in Python 3.9+ for path-aware boundary checks.
  • Publicly accessible job APIs need extra scrutiny — when job metadata can be influenced by external users, every field that touches the filesystem is a potential traversal vector.

How Orbis AppSec Detected This

  • Source: The input_path field originates from job["input_path"], which is populated from user-supplied job submission data.
  • Sink: Path(job["input_path"]) at WAVE2SCORE/app.py:58, subsequently passed to a subprocess.run call that invokes main.py with the path as an argument.
  • Missing control: No call to .resolve() and no check that the resulting path falls within WORK_DIR before the path was used.
  • CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ("Path Traversal").
  • Fix: Added .resolve() to canonicalize input_path and a startswith(WORK_DIR.resolve()) guard that raises RuntimeError for any path outside the work directory.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

Path traversal vulnerabilities are deceptively simple: a missing .resolve() call and a missing boundary check are all it takes to turn a routine file-processing function into an unauthorized file-access primitive. In WAVE2SCORE/app.py, the run_job() function trusted that job["input_path"] would always point somewhere safe — a trust that an attacker could easily betray.

The fix demonstrates a key principle of defensive programming: canonicalize first, validate second, act third. By resolving the path to its true absolute form before comparing it to WORK_DIR, the application can no longer be fooled by .. tricks or symlink chains. Two lines of code, one closed attack surface.

If your application processes user-supplied file paths in any form — uploaded filenames, job metadata, API parameters — apply this same pattern everywhere. It is cheap to write and expensive to skip.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #5

Related Articles

critical

How path traversal happens in PHP virtual filesystem adapters and how to fix it

A critical path traversal flaw in `VirtualAdapter.php`'s `resolveMount()` method allowed attackers to escape mounted directory boundaries using sequences like `../../../etc/passwd`. The fix introduces `PathPolicy::normalizeRelative()` to sanitize the remaining path segment before it ever reaches the underlying storage adapter.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

high

How Trust-Prefix Bypass via Path Traversal Happens in Python Copier and How to Fix It

CVE-2026-53951 is a high-severity path traversal vulnerability in Copier 9.15.0 that allowed attackers to bypass trust-prefix checks and execute tasks without user confirmation. Upgrading to Copier 9.15.2 eliminates this attack vector by properly validating file paths before task execution.

critical

How Path Traversal in basic-ftp Leads to File Overwrite Attacks and How to Fix It

CVE-2026-27699 is a critical path traversal vulnerability in basic-ftp versions before 5.3.1 that allows attackers to overwrite arbitrary files on the system by crafting malicious file paths. This vulnerability was fixed by upgrading basic-ftp and enforcing strict version constraints across dependent packages. Understanding this attack and its mitigation is essential for developers using FTP libraries in production environments.

critical

How Command Injection Vulnerabilities Happen in Python Subprocess Calls and How to Fix Them

A critical command injection vulnerability was discovered in `src/unused/server/fft.py` where external binaries like `oggenc` and `cocoa_text` were executed with file path parameters that could be manipulated by user input. Although `shell=False` was used, the lack of input validation allowed attackers to potentially trigger processing of arbitrary files or cause denial of service. This fix implements proper path validation to prevent exploitation.

critical

How Path Traversal happens in Node.js Express servers and how to fix it

A path traversal vulnerability in `src/server.js` allowed attackers to escape the intended wiki directory by sending encoded traversal sequences through the `/api/pages/:slug(*)` wildcard endpoint. The flawed `startsWith` boundary check could be bypassed after `decodeURIComponent` processing, potentially exposing arbitrary files on the server. The fix replaces the inline filesystem logic with a dedicated `readWikiPage()` function that enforces proper path validation.