django/django · critical · SuspiciousFileOperation
Detected path traversal attempt in '%s'
Error message
Detected path traversal attempt in '%s'
What it means
Raised by Storage.get_available_name() (django/core/files/storage/base.py:83) as SuspiciousFileOperation when pathlib.PurePath(dir_name).parts contains '..'. This is an active security guard: it stops a supplied filename like '../secret' from escaping the storage root. It fires during save() before any name is written.
Source
Thrown at django/core/files/storage/base.py:83
return get_valid_filename(name)
def get_alternative_name(self, file_root, file_ext):
"""
Return an alternative filename, by adding an underscore and a random 7
character alphanumeric string (before the file extension, if one
exists) to the filename.
"""
return "%s_%s%s" % (file_root, get_random_string(7), file_ext)
def get_available_name(self, name, max_length=None):
"""
Return a filename that's free on the target storage system and
available for new content to be written to.
"""
name = str(name).replace("\\", "/")
dir_name, file_name = os.path.split(name)
if ".." in pathlib.PurePath(dir_name).parts:
raise SuspiciousFileOperation(
"Detected path traversal attempt in '%s'" % dir_name
)
validate_file_name(file_name)
file_ext = "".join(pathlib.PurePath(file_name).suffixes)
file_root = file_name.removesuffix(file_ext)
# If the filename is not available, generate an alternative
# filename until one is available.
# Truncate original name if required, so the new filename does not
# exceed the max_length.
while not self.is_name_available(name, max_length=max_length):
# file_ext includes the dot.
name = os.path.join(
dir_name, self.get_alternative_name(file_root, file_ext)
)
if max_length is None:
continue
# Truncate file_root if max_length exceeded.
truncation = len(name) - max_lengthView on GitHub (pinned to ae25a40be0)
Solutions
- Sanitize user-supplied filenames with get_valid_filename() before save().
- Make upload_to callables return plain names with no directory components.
- Strip '..' and leading slashes from incoming filenames at the trust boundary.
- Catch SuspiciousFileOperation and reject the upload with a 400 response.
Example fix
// before storage.save(request.POST['filename'], upload) # ../x -> blocked // after from django.utils.text import get_valid_filename storage.save(get_valid_filename(request.POST['filename']), upload)
Defensive patterns
Strategy: validation
Validate before calling
import pathlib
from django.utils.text import get_valid_filename
def safe_storage_name(name):
name = str(name).replace('\\', '/')
path = pathlib.PurePosixPath(name)
if path.is_absolute() or '..' in path.parts:
raise ValueError(f'Unsafe path: {name}')
return get_valid_filename(name) Type guard
import pathlib
def is_safe_relative_path(name):
path = pathlib.PurePosixPath(str(name).replace('\\', '/'))
return not path.is_absolute() and '..' not in path.parts Try / catch
from django.core.exceptions import SuspiciousFileOperation
try:
storage.save(name, f)
except SuspiciousFileOperation:
# reject upload with a 400
raise PermissionError('Invalid filename') Prevention
- Run user-supplied filenames through get_valid_filename() before save.
- Make upload_to callables return plain names without '..' segments.
- Never trust client-provided paths; use os.path.basename on segments.
- Catch SuspiciousFileOperation at the view boundary and return 400.
When it happens
Trigger: Calling storage.save('../etc/passwd', file); a FileField upload_to returning '../../media/x'; user-uploaded filenames containing '..' passed unvalidated to save(); get_available_name() being called directly with a traversal path. The check runs on the directory portion after os.path.split.
Common situations: Accepting user-supplied filenames without sanitization; misconfigured upload_to callables returning relative escapes; zip-file extraction code passing member names verbatim to storage; migration of legacy data with malformed paths.
Related errors
- Storage can not find an available filename for "%s". Please
- Could not derive file name from '%s'
- Detected path traversal attempt in '%s'
- The SECRET_KEY setting must not be empty.
- The field %s cannot be referenced.
AI-assisted analysis of django/django@ae25a40be0 (2026-08-06).
Data as JSON: /api/errors/3567f64037de360c.
Report an issue: GitHub.