invoke-ai/InvokeAI · error · ValueError
Cannot derive a safe filename for {url} from '{file_name}'
Error message
Cannot derive a safe filename for {url} from '{file_name}' What it means
When the destination is a directory, the library derives a filename from the URL's last path segment (or the Content-Disposition header). Before using it, _validate_filename checks the name won't escape the destination (path traversal, '..', absolute paths, unsafe characters). If validation fails, ValueError is raised because no safe filename could be derived from the attacker-influenced URL.
Source
Thrown at invokeai/app/services/download/download_default.py:512
job.content_type = resp.headers.get("Content-Type")
job.etag = resp.headers.get("ETag") or job.etag
job.last_modified = resp.headers.get("Last-Modified") or job.last_modified
content_length = int(resp.headers.get("content-length", 0))
if job.dest.is_dir():
parsed_url = urlparse(str(url))
file_name = os.path.basename(parsed_url.path) # default is to use the last bit of the URL
if match := re.search('filename="(.+)"', resp.headers.get("Content-Disposition", "")):
remote_name = match.group(1)
if self._validate_filename(job.dest.as_posix(), remote_name):
file_name = remote_name
# The URL path is attacker-influenced too -- a final segment of ".." would
# otherwise put download_path one level above dest.
if not self._validate_filename(job.dest.as_posix(), file_name):
raise ValueError(f"Cannot derive a safe filename for {url} from '{file_name}'")
job.download_path = job.dest / file_name
else:
job.dest.parent.mkdir(parents=True, exist_ok=True)
job.download_path = job.dest
assert job.download_path
in_progress_path = self._in_progress_path(job.download_path)
if resume_from > 0 and resp.status_code == 200:
# Server ignored Range. Restart download from scratch.
job.resume_required = True
job.resume_message = "Resume refused by server. Restart required."
job.pause()
raise DownloadJobCancelledException("Resume refused by server. Restart required.")
View on GitHub (pinned to 0b6a024f2f)
Solutions
- Fix the source URL so its final path segment is a real filename (e.g. .../model.safetensors instead of .../model.safetensors/ or a bare directory).
- Download to an explicit full file path instead of a directory so no filename derivation from the URL is needed.
- Sanitize the URL before submitting (strip trailing slashes and query strings, ensure a non-traversal basename).
- If the Content-Disposition header is the culprit, the host is sending hostile headers — download from a trusted mirror.
Example fix
// before: URL ending in '/' — basename is empty/unsafe
queue.submit(HttpSource(url='https://example.com/models/v2/'), Path('/models'))
// after: full file path, or a proper file URL
queue.submit(HttpSource(url='https://example.com/models/v2/model.safetensors'), Path('/models')) Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlparse
import os
def url_has_safe_basename(url: str) -> bool:
name = os.path.basename(urlparse(url).path)
return bool(name) and name not in ('.', '..') and not name.startswith('/') and '\\' not in name and '\x00' not in name Type guard
def is_safe_filename(name: str) -> bool:
return bool(name) and name not in ('.', '..') and '/' not in name and '\\' not in name and not name.startswith('.') Try / catch
try:
queue.submit(HttpSource(url=url), dest_dir)
except ValueError as e:
if 'Cannot derive a safe filename' in str(e):
fixed_url = url.rstrip('/')
assert url_has_safe_basename(fixed_url), f'bad url: {url}'
queue.submit(HttpSource(url=fixed_url), dest_dir) Prevention
- Always end source URLs with a real filename — no trailing slash or bare directory links.
- When possible download to an explicit file path instead of a directory dest.
- Sanitize batch-import URL lists automatically (strip query, reject '..' segments) before submission.
When it happens
Trigger: Downloading to a directory (job.dest is_dir()) where the URL's final path segment is '..', empty, an absolute path, or the Content-Disposition filename fails the same validation — i.e. any URL like https://host/downloads/ or https://host/path/..%2F..
Common situations: Hand-written model URLs ending in '/' so basename() yields '' or '..'; redirects to URLs with hostile paths; batch-import lists with malformed URLs; downloading to a directory dest with a query-only URL.
Related errors
- only relative download paths accepted
- Invalid image name, potential directory traversal detected
- Parent directory references not allowed in subfolder path
- Download URL '{url}' has no host.
- Download URL '{url}' has an invalid port.
AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29).
Data as JSON: /api/errors/ea90ccb2d40e5b78.
Report an issue: GitHub.