highUser input reaching your database or shell
File Path Built From Request Data
A file path is built from a name the caller controls.
Why it matters
"../../etc/passwd" is a valid file name to os.path.join, and it happily walks up out of your uploads folder. Stripping the directory part with secure_filename and checking the resolved path stays inside the base folder is a two-line fix that keeps the feature intact.
What it looks like
This is the shape of code that triggers the rule. AI tools produce it because it works, and nothing tells them it is unsafe.
send_file with a joined query parameter
@app.route('/download')
def download():
return send_file(os.path.join(UPLOAD_DIR, request.args.get('name')))The smallest fix
minimal patch
from pathlib import Path
from werkzeug.utils import secure_filename
base = Path(UPLOAD_DIR).resolve()
target = (base / secure_filename(name)).resolve()
if not target.is_relative_to(base):
abort(400)
return send_file(target)The better fix
If you have a few more minutes, this is the approach that holds up as the app grows.
safe alternative
# Flask: send_from_directory(UPLOAD_DIR, name) does the containment
# check for you. Better still, store files under ids you generate, look
# the id up in the database and never accept a file name from the client.Let your AI tool fix it
When the scanner finds this in your project, it fills in the file and line for you. This is the prompt it gives you to paste into Claude Code, Cursor or whatever you use.
In [the file] at line [the line number] a file path is built from request data: [the flagged code]. Sanitise the name with secure_filename, resolve it against a fixed base directory, and reject it unless the resolved path is inside that base (Path.is_relative_to). For downloads in Flask, use send_from_directory. Add a test that requests "../" and expects a 400.
How to check the fix worked
1. Request the endpoint with a name containing "../" and confirm a 400. 2. Request a real file name and confirm it is served.