That exception looks like Flask-Uploads is trying to from werkzeug import secure_filename which should be from werkzeug.utils import secure_filename, as per your own code.

Going by the Flask-Uploads github repo this appears to have been fixed 12 months ago.

I'd try pip install -U flask-uploads in your virtual environment, to ensure the latest version.

EDIT:

As @mattficke points out, the PyPi version is dated, and there's not a more recent release on the repo. Turns out you can install directly based on a commit hash, so for the latest (at the time of writing this):

pip install git+https://github.com/maxcountryman/flask-uploads.git@f66d7dc

Or in a requirements.txt:

git+https://github.com/maxcountryman/flask-uploads.git@f66d7dc

Then pip install -r requirements.txt.

Which works wonders:

>>> from flask_uploads import UploadSet,configure_uploads,IMAGES,DATA,ALL
>>> # No Exception
Answer from v25 on Stack Overflow
🌐
Beautiful Soup
tedboy.github.io › flask › generated › werkzeug.secure_filename.html
werkzeug.secure_filename — Flask API
werkzeug.secure_filename · View page source · werkzeug.secure_filename(filename)[source]¶ · Pass it a filename and it will return a secure version of it. This filename can then safely be stored on a regular file system and passed to os.path.join(). The filename returned is an ASCII only ...
🌐
Werkzeug
werkzeug.palletsprojects.com › en › stable › utils
Utilities — Werkzeug Documentation (3.1.x)
werkzeug.utils.secure_filename(filename)¶ · Pass it a filename and it will return a secure version of it. This filename can then safely be stored on a regular file system and passed to os.path.join(). The filename returned is an ASCII only string for maximum portability.
Discussions

'Secure' Filenames
An example: if the user supplied a file name of ../../../../../boot/vmlinuz and your app was running as root -bad practice but still common - you might write over your Linux kernel with the uploaded file if your didn't sanitize the input. More on reddit.com
🌐 r/Python
4
6
January 14, 2017
ImportError: cannot import name 'secure_filename' from 'werkzeug'
Hello, thanks for this great package! I recently tried updating to version 0.3.7 but encountered this error as soon as my project tried to import adminui: File " ", line , in import adminui File " \lib\site... More on github.com
🌐 github.com
10
September 21, 2020
Import Error when importing secure_filename from Werkzeug.utils : Forums : PythonAnywhere
2020-06-09 19:19:39,700: Error ... name 'secure_filename' 2020-06-09 19:19:39,731: File "/var/www/virtualta_pythonanywhere_com_wsgi.py", line 16, in 2020-06-09 19:19:39,731: from flask_app import app as application # noqa 2020-06-09 19:19:39,731: 2020-06-09 19:19:39,731: File "/home/VirtualTA/mysite/flask_app.py", line 6, in 2020-06-09 19:19:39,731: from werkzeug.utils import ... More on pythonanywhere.com
🌐 pythonanywhere.com
secure_filename in werkzeug.util is problematic when the file name isnon-ASCII characters
if the file name is non-ASCII characters then using secure_filename will result become wired so How can I solve this problem If I'm going to do my own secure_filename where should I put my code... More on github.com
🌐 github.com
3
March 29, 2016
🌐
Medium
medium.com › @sujathamudadla1213 › what-is-the-use-of-secure-filename-in-flask-9eef4c71503b
What is the use of secure_filename in flask? | by Sujatha Mudadla | Medium
November 25, 2023 - secure_filename is a function provided by Flask's werkzeug.utils module that is used to sanitize and secure filenames before storing them on the server.
🌐
Reddit
reddit.com › r/python › 'secure' filenames
r/Python on Reddit: 'Secure' Filenames
January 14, 2017 -

Flask's WSGI library werkzeug has a utility function called secure_filename - you're intended to pass the filename of a user uploaded file to it but I'm having trouble understanding what exactly the concern is, and what securing a filename could possibly entail.

The Pooco site notes

what does that secure_filename() function actually do? Now the problem is that there is that principle called “never trust user input”. This is also true for the filename of an uploaded file. All submitted form data can be forged, and filenames can be dangerous. For the moment just remember: always use that function to secure a filename before storing it directly on the filesystem.

What's returned is the exact same filename except I guess sanitized in some way. What my concern is is that I don't really understand what this function accomplishes. I plan on changing the filename of any user submitted file anyways (random uuid string with a suffix of '-small', '-large', etc.). Does it do me any good to first run the filename through secure_filename() and then change the actual filename? Can I just initially change the filename?

🌐
GitHub
github.com › pallets › werkzeug › blob › main › src › werkzeug › utils.py
werkzeug/src/werkzeug/utils.py at main · pallets/werkzeug
def secure_filename(filename: str) -> str: r"""Pass it a filename and it will return a secure version of it. This · filename can then safely be stored on a regular file system and passed · to :func:`os.path.join`. The filename ...
Author   pallets
🌐
ProgramCreek
programcreek.com › python › example › 60712 › werkzeug.secure_filename
Python Examples of werkzeug.secure_filename
Optionnal with a :class:`~werkzeug.FileStorage` but allow to override clietn value :param string prefix: a path or a callable returning a path to be prepended to the filename. :param bool overwrite: if specified, override the storage default value. :raise UnauthorizedFileType: If the file type is not allowed ''' if not filename and isinstance(file_or_wfs, FileStorage): filename = lower_extension(secure_filename(file_or_wfs.filename)) if not filename: raise ValueError('filename is required') if not self.file_allowed(file_or_wfs, filename): raise UnauthorizedFileType() if prefix: filename = '/'.
🌐
GitHub
github.com › bigeyex › python-adminui › issues › 14
ImportError: cannot import name 'secure_filename' from 'werkzeug' · Issue #14 · bigeyex/python-adminui
September 21, 2020 - Meanwhile, the latest version of Werkzeug has moved secure_filename from being importable via werkzeug directly to now only being importable via werkzeug.utils.
Author   bigeyex
Find elsewhere
🌐
ProgramCreek
programcreek.com › python › example › 96284 › werkzeug.utils.secure_filename
Python Examples of werkzeug.utils.secure_filename
Ensures that the file <ul> <li>has any of the extensions listed in SUPPORTED_EXTENSIONS</li> <li>exists and is a file (not a directory) if "mustExist" is set to True</li> </ul> @param filename the name of the file for which to determine the absolute path @param mustExist if set to true, the ...
🌐
Snyk
snyk.io › advisor › werkzeug › functions › werkzeug.utils.secure_filename
How to use the werkzeug.utils.secure_filename function in Werkzeug | Snyk
def upload_file(): if request.method == 'POST': # check if the post request has the file part if 'file' not in request.files: return "no file sended" file = request.files['file'] # if user does not select file, browser also # submit a empty part without filename if file.filename == '': return "no filename" if file and allowed_file(file.filename): filename = secure_filename(file.filename) file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return processfile(filename) return
🌐
PythonAnywhere
pythonanywhere.com › forums › topic › 27730
Import Error when importing secure_filename from Werkzeug.utils : Forums : PythonAnywhere
from werkzeug.utils import secure_filename from flask import Flask, redirect, render_template, request, url_for from flask_bootstrap import Bootstrap from .helper import upload_file_to_s3 app = Flask(__name__) Bootstrap(app) app.config["DEBUG"] = True app.config.from_object("mysite.config") comments = [] ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'} @app.route("/", methods=["GET", "POST"]) def main(): if request.method == "GET": return render_template("main_page.html", comments=comments) comments.append(request.form["contents"]) return redirect(url_for('main')) #https://www.
🌐
GitHub
github.com › pallets › werkzeug › issues › 883
secure_filename in werkzeug.util is problematic when the file name isnon-ASCII characters · Issue #883 · pallets/werkzeug
March 29, 2016 - pallets / werkzeug Public · There was an error while loading. Please reload this page. Notifications · You must be signed in to change notification settings · Fork 1.8k · Star 6.9k · New issueCopy link · New issueCopy link · Closed · Closed · secure_filename in werkzeug.util is problematic when the file name isnon-ASCII characters#883 ·
Author   pallets
🌐
Beautiful Soup
tedboy.github.io › flask › _modules › werkzeug › utils.html
werkzeug.utils — Flask API
This filename can then safely be stored on a regular file system and passed to :func:`os.path.join`. The filename returned is an ASCII only string for maximum portability. On windows systems the function also makes sure that the file is not named after one of the special device files. >>> secure_filename("My cool movie.mov") 'My_cool_movie.mov' >>> secure_filename("../../../etc/passwd") 'etc_passwd' >>> secure_filename(u'i contain cool \xfcml\xe4uts.txt') 'i_contain_cool_umlauts.txt' The function might return an empty filename.
🌐
GitHub
github.com › pallets › werkzeug › issues › 1724
werkzeug fails to import secure_filename within Docker · Issue #1724 · pallets/werkzeug
February 9, 2020 - Traceback (most recent call last): File "db_setup.py", line 1, in <module> from app import db File "/usr/src/app/app.py", line 6, in <module> from flask_uploads import UploadSet, configure_uploads File "/usr/local/lib/python3.8/site-packages/flask_uploads.py", line 26, in <module> from werkzeug import secure_filename, FileStorage ImportError: cannot import name 'secure_filename' from 'werkzeug' (/usr/local/lib/python3.8/site-packages/werkzeug/__init__.py)
Author   pallets
Top answer
1 of 1
7

You are correct.

You only need to use the secure_filename function if you are using the value of request.files['file'].filename to build a filepath destined for your filesystem - for example as an argument to os.path.join.

As you're using a UUID for the filename, the user input value is disregarded anyway.

Even without S3, it would also be safe NOT to use secure_filename if you used a UUID as the filename segment of the filepath on your local filesystem. For example:

uploaded_file = request.files['file']
if uploaded_file:
    file_uuid = uuid.uuid4()
    file.save(os.path.join(app.config['UPLOAD_FOLDER'], file_uuid))
    # Rest of code

In either scenario you'd then store the UUID somewhere in the database. Whether you store the originally provided request.files['file'].filename value alongside that is your choice.

This might make sense if you want the user to see the original name of the file when they uploaded it. In that case it's definitey wise to run the value through secure_filename anyway, so there's never a situation where the frontend displays a listing to a user which includes a file called ../../../../ohdear.txt


the secure_filename docstring also points out some other functionality:

Pass it a filename and it will return a secure version of it. This filename can then safely be stored on a regular file system and passed to :func:os.path.join. The filename returned is an ASCII only string for maximum portability. On windows systems the function also makes sure that the file is not named after one of the special device files.

>>> secure_filename("My cool movie.mov")
'My_cool_movie.mov'
>>> secure_filename("../../../etc/passwd")
'etc_passwd'
>>> secure_filename(u'i contain cool \xfcml\xe4uts.txt')
'i_contain_cool_umlauts.txt'
🌐
TestDriven.io
testdriven.io › tips › 71b62504-39d7-43ee-8ca2-715a42fc97c7
Tips and Tricks - Flask File Uploads | TestDriven.io
import os from flask import request, current_app from werkzeug.utils import secure_filename @journal_blueprint.route('/upload_file', methods=['POST']) def upload_file(): if 'file' in request.files: file = request.files['file'] filename = secure_filename(file.filename) file.save(os.path.join(current_app.config['UPLOAD_FOLDER'], filename)) return '<p>File uploaded!</p>'
🌐
GitHub
github.com › GoogleCloudPlatform › getting-started-python › issues › 256
bookshelf error on App Engine: "ImportError: cannot import name 'secure_filename' from 'werkzeug'" · Issue #256 · GoogleCloudPlatform/getting-started-python
February 11, 2020 - Traceback (most recent call last): ... import(module) File "/srv/main.py", line 22, in import storage File "/srv/storage.py", line 23, in from werkzeug import secure_filename ImportError: cannot import name 'secure_filename' from 'werkzeug' (/env/lib/python3.7/site-packages/wer...
Author   GoogleCloudPlatform
🌐
Flask
flask.palletsprojects.com › en › stable › patterns › fileuploads
Uploading Files — Flask Documentation (3.1.x)
import os from flask import Flask, flash, request, redirect, url_for from werkzeug.utils import secure_filename UPLOAD_FOLDER = '/path/to/the/uploads' ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'} app = Flask(__name__) app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
Top answer
1 of 3
8

Flask-Uploads is actually using the patterns found in Flask's documentation for file upload handling. It uses werkzeug.secure_filename, it provides a way to set MAX_CONTENT_LENGTH if, for some reason, you are using Flask 0.5 or older, and it provides a way to validate files based on their extension.

In fact, Flask's documentation actually explicitly suggests using Flask-Uploads:

Because the common pattern for file uploads exists almost unchanged in all applications dealing with uploads, there is a Flask extension called Flask-Uploads that implements a full fledged upload mechanism with white and blacklisting of extensions and more.

2 of 3
0

I ended up with using my own secure_filename in my case that supports Arabic:

import os
import re
import unicodedata

# edit this to add more characters
_filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9\u0600-\u06FF_.-]")
_windows_device_files = {
    "CON",
    "PRN",
    "AUX",
    "NUL",
    *(f"COM{i}" for i in range(10)),
    *(f"LPT{i}" for i in range(10)),
}

def secure_filename(filename: str) -> str:
    # Normalize the filename to handle UTF-8 characters
    filename = unicodedata.normalize("NFKC", filename)

    # Replace path separators with spaces
    for sep in os.sep, os.path.altsep:
        if sep:
            filename = filename.replace(sep, " ")

    # Remove unsafe characters
    filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip("._")

    # Ensure the filename is not a reserved device name on Windows
    if os.name == "nt" and filename and filename.split(".")[0].upper() in _windows_device_files:
        filename = f"_{filename}"

    return filename