Safeguard
Security Guides•

Is secure_filename enough to make Flask file uploads safe?

secure_filename blocks path traversal in filenames, but not content, size, type or how files are served. A fuller Flask upload checklist to follow.

Safeguard Security Team
4 min read

No. Werkzeug's secure_filename stops a client from using a filename like ../../home/user/.bashrc to write outside your upload folder, but it says nothing about what is inside the file, how big it is, or how it will be served later. Treat it as one control in a checklist.

What secure_filename protects against

Flask's file upload pattern starts from the principle never trust user input, which applies to the uploaded file's name as well. The docs show the danger: imagine a client sends a filename made of repeated parent directory segments leading to a path such as home/username/.bashrc. If you join that with your UPLOAD_FOLDER, the user may be able to modify a file on the server that they should not.

secure_filename reduces a name to a safe form, stripping directory components so the file can only be saved inside the folder you chose:

from werkzeug.utils import secure_filename

filename = secure_filename(file.filename)
file.save(os.path.join(app.config["UPLOAD_FOLDER"], filename))

What it does not do

Concernsecure_filename helps?
Path traversal through the nameYes
File content is what the extension saysNo
File sizeNo, use MAX_CONTENT_LENGTH
Overwriting another user's file with the same nameNo
Serving HTML or script from uploadsNo
Malware or oversized imagesNo

The Flask docs add that you should limit allowed extensions, because you probably do not want users uploading everything, for example HTML files that would run script on your domain when served. Their example uses an allow list of txt, pdf, png, jpg, jpeg and gif. An extension check is still a hint, since an attacker controls the name. Check the actual content for expected types where it matters.

A fuller checklist

  1. Use secure_filename, or better, ignore the user's name and generate your own, for example a random identifier plus a safe extension.
  2. Store uploads outside the web root and outside the application code directory.
  3. Set MAX_CONTENT_LENGTH and per route limits so a large body is refused early.
  4. Allow only the extensions and content types you need, and verify content for images and documents.
  5. Never overwrite existing files. Generated names avoid collisions and attacks on other users' files.
  6. Serve uploads with a correct content type and the Content-Disposition attachment header, as Flask's security page advises for files from uploads. The same page warns not to send out HTML from uploaded files.
  7. Consider serving user content from a separate domain so that any script runs without your cookies.
  8. Scan files if your users share them with each other.

The serving side is where XSS comes in

Flask's security page notes that browsers may guess the content type of text files from the first bytes, so a crafted file could be treated as HTML. Combine an explicit content type with attachment disposition to remove the guesswork.

Using send_from_directory

When you return files to users, use the framework helper that limits the path to a directory rather than joining paths yourself, and never pass user input straight to a file open call.

Try it yourself

Click through it at your own pace, or open it full screen. Look at how path handling and file operations are flagged.

Quick check

Should I trust the file extension?

Only as a first filter. The client controls the filename, so verify content for types that matter.

Is a random filename better than secure_filename?

Often yes. It avoids collisions and removes the user's text from your file system. Keep the original name as metadata if you need it.

Safeguard's SAST and dependency scanning can flag unsafe file handling in code and vulnerable Flask or Werkzeug versions. Try a free scan.

Sources

Last reviewed: 19 October 2026.

Never miss an update

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

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.