Flask Insecure Static File Serve

MEDIUM

Detects use of send_file() and send_from_directory() which serve files from the server's file system and are vulnerable to path traversal when the filename argument comes from user input.

Rule Information

Language
Python
Category
Flask
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonflaskpath-traversalstatic-filesfile-disclosuresend-filesend-from-directoryauditCWE-73OWASP-A01
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-FLASK-SEC-017 --project .
1
2
3
4
5
6
7
8
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

About This Rule

Understanding the vulnerability and how it is detected

This rule detects calls to Flask's send_file() and send_from_directory() functions, which serve files directly from the server's file system in HTTP responses. When the filename or path argument to these functions originates from user input (request parameters, URL path segments, form fields), the application is vulnerable to path traversal attacks.

Path traversal (also called directory traversal) occurs when an attacker supplies sequences like ../../../etc/passwd or URL-encoded equivalents (%2e%2e%2f) as the filename. If the application does not sanitize the filename before passing it to send_from_directory() or send_file(), the server will read and return files outside the intended directory -- including application source code, configuration files, private keys, and system files.

send_from_directory() provides partial protection by joining the directory and filename arguments, but it does not prevent traversal if the filename itself contains ../ sequences unless werkzeug.utils.safe_join() or secure_filename() is applied first. send_file() with an absolute path constructed from user input is even more dangerous.

The detection uses Or(calls("send_from_directory"), calls("flask.send_from_directory"), calls("send_file"), calls("flask.send_file")) to flag any use of these functions for manual review. Not every call is vulnerable -- serving a hardcoded filename is safe -- but every call warrants inspection to verify the filename argument cannot be user-controlled.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Arbitrary File Read via Path Traversal

An attacker who can control the filename argument can read any file readable by the web server process. Common targets include /etc/passwd, /etc/shadow (if the server runs as root), application source code, .env files with credentials, SSL private keys, and database files. In Flask applications, the config object and SECRET_KEY are frequently stored in files adjacent to the application.

2

Source Code and Configuration Disclosure

Flask applications commonly store configuration in app.cfg, .env, or config.py files in the project root. A path traversal in a file-serving route can expose these files directly, leaking database credentials, API keys, and Flask's SECRET_KEY (which enables session forgery).

3

Private Key and Certificate Theft

Web servers often store TLS private keys, SSH keys, or JWT signing keys on disk. A path traversal vulnerability can expose these keys, enabling an attacker to impersonate the server, decrypt captured traffic, or forge authentication tokens.

4

Denial of Service via Large File Reads

An attacker can cause the server to read and stream large files (disk images, log files, binary blobs), consuming server memory and network bandwidth and degrading availability for legitimate users.

How to Fix

Recommended remediation steps

  • 1Always sanitize filenames from user input with werkzeug.utils.secure_filename() before passing to send_file() or send_from_directory(). secure_filename() strips all directory components and dangerous characters.
  • 2Verify that the resolved file path is inside the intended directory by checking os.path.realpath(path).startswith(os.path.realpath(base_dir)) after joining.
  • 3Prefer serving user-uploaded files by a server-generated identifier (UUID) stored in a database rather than by the original filename. Map UUID to filename server-side.
  • 4Set the as_attachment=True flag when serving user-uploaded files to force the browser to download rather than render them, reducing the risk of stored XSS via uploaded HTML/SVG files.
  • 5Use a dedicated file storage service (object storage) or a web server (nginx) for static file serving instead of routing all file downloads through Flask application code.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule uses Or(calls("send_from_directory"), calls("flask.send_from_directory"), calls("send_file"), calls("flask.send_file")) to match both the directly imported function forms and the module-qualified forms. This is a broad audit pattern -- every call to these functions is flagged for manual review, regardless of whether the filename argument is demonstrably user-controlled. The rule surfaces all file-serving locations for inspection. For a dataflow rule that specifically traces user input from Flask request parameters into send_file() or send_from_directory(), a taint-analysis companion rule would complement this audit coverage.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A01:2021 - Broken Access Control: prevent path traversal to files outside intended directories
CWE Top 25
CWE-73 External Control of File Name or Path
PCI DSS v4.0
Requirement 6.2.4 -- prevent path traversal and unauthorized file access
NIST SP 800-53
AC-3: Access Enforcement -- restrict file system access to authorized paths

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Flask Insecure Static File Serve

No. If the filename is a hardcoded string or a server-generated UUID with no user input, there is no path traversal risk. This is an audit-grade rule -- it flags every call for review to verify the filename source. Only calls where the filename comes from request.args, request.form, URL path parameters, or other user-controlled sources are actually vulnerable.
send_from_directory() joins the directory and filename using werkzeug's safe_join() internally, which does provide some protection. However, if the filename contains URL-encoded traversal sequences (%2e%2e%2f) that are decoded before reaching safe_join(), or if the filename is joined with other user-controlled path segments before being passed, traversal is still possible. Always apply secure_filename() first.
werkzeug.utils.secure_filename() strips all directory components (/ and \), removes leading dots, and replaces dangerous characters with underscores. The result is a filename that is safe to use as a file system path component. Note that it does not validate that the file exists or that the caller has permission to access it -- those checks are separate.
secure_filename() returns an empty string if the input contains no safe characters. Always check for an empty result and abort with a 400 error. Passing an empty string to send_from_directory() will raise an error, but it is better to handle this explicitly.
Prefer send_from_directory() because it always takes a directory and filename as separate arguments and uses safe_join() internally. send_file() accepts an absolute path string, which is more dangerous if any part of the path is user-controlled.
Run: pathfinder ci --ruleset python/flask/PYTHON-FLASK-SEC-017 --project . The rule outputs SARIF, JSON, or CSV and can post inline pull request comments on GitHub.
Store user-uploaded files in object storage (S3, GCS, Azure Blob) and serve them via pre-signed URLs. This removes the Flask application entirely from the file-serving path, eliminates path traversal risk, and scales independently of the application server. If local storage is required, serve files through a dedicated nginx location block with the X-Accel-Redirect pattern rather than through Flask application code.

New feature

Get these findings posted directly on your GitHub pull requests

The Flask Insecure Static File Serve rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works