unslothai/unsloth · error · HTTPException
Invalid image filename.
Error message
Invalid image filename.
What it means
HTTP 400 from _safe_dataset_image_path: the requested image filename failed the lexical safety check — it contains '/', '\\', '..', a NUL byte, or does not equal its own Path(raw).name (i.e. it is not a single clean path component). This is the first, string-level guard against path traversal before any filesystem access. A second, identical 400 exists after resolve() as defense in depth.
Source
Thrown at studio/backend/routes/training.py:3673
)
except (OSError, UnidentifiedImageError, ValueError):
return # not a decodable image -> not a bomb; leave the existing contract
if width > _MAX_TRAINING_IMAGE_SIDE or height > _MAX_TRAINING_IMAGE_SIDE:
raise HTTPException(
status_code = 400,
detail = (
f"Image '{original_name}' is too large ({width}x{height}); maximum is "
f"{_MAX_TRAINING_IMAGE_SIDE}px per side."
),
)
def _safe_dataset_image_path(folder: Path, filename: str) -> Path:
"""Resolve ``filename`` to an image path strictly inside ``folder``. Rejects any path
separators / traversal / null bytes and non-image extensions."""
raw = filename or ""
if "/" in raw or "\\" in raw or ".." in raw or "\x00" in raw or raw != Path(raw).name:
raise HTTPException(status_code = 400, detail = "Invalid image filename.")
if Path(raw).suffix.lower() not in _DIFFUSION_DATASET_IMAGE_EXTS:
exts = ", ".join(sorted(_DIFFUSION_DATASET_IMAGE_EXTS))
raise HTTPException(status_code = 400, detail = f"Not an image file. Allowed: {exts}")
path = folder / raw
# Defense in depth: the real path must stay under the dataset folder.
try:
path.resolve().relative_to(folder.resolve())
except ValueError:
raise HTTPException(status_code = 400, detail = "Invalid image filename.")
return path
def _load_metadata_captions(folder: Path) -> dict[str, str]:
"""Read metadata.jsonl / captions.jsonl into {file_name: caption}, mirroring the
trainer's discovery (keys file_name/video/image/file; caption in the ``text`` column)."""
import json
out: dict[str, str] = {}View on GitHub (pinned to 203007d190)
Solutions
- Pass only the bare filename (single component, no directories): GET /training/diffusion/dataset/myset/image/cat.png — never a path.
- Fix the client to use Path(filename).name (or basename) before filling the URL parameter.
- If you genuinely need nested layout, flatten it: images must live directly in the dataset folder by contract.
Example fix
# before filename = str(relative_path) # 'day1/cat.png' -> 400 # after from pathlib import PurePosixPath filename = PurePosixPath(relative_path).name # 'cat.png'
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def is_safe_image_filename(raw: str) -> bool:
return bool(raw) and '/' not in raw and '\\' not in raw \
and '..' not in raw and '\x00' not in raw and raw == Path(raw).name Type guard
def is_invalid_filename_error(exc: HTTPException) -> bool:
return exc.status_code == 400 and exc.detail == 'Invalid image filename.' Try / catch
try:
await api.get(f'/training/diffusion/dataset/{name}/image/{quote(filename)}')
except HTTPStatusError as e:
if e.response.status_code == 400 and e.response.json()['detail'] == 'Invalid image filename.':
filename = Path(filename).name # sanitize once, retry with bare name
raise_if_still_unsafe(filename) Prevention
- Always send Path(name).name, never a joined path, in image URL parameters.
- URL-encode filenames (quote()) so separators are visible at the client, not decoded server-side.
- Reject filenames containing '..' or separators in your own input layer.
When it happens
Trigger: Calling an image-scoped dataset route (serve image, update caption, delete image) with a filename parameter like '../../etc/passwd', 'sub/dir/img.png', 'img\x00.png', or anything that is not a bare filename. Also triggered by URL-encoded separators (%2F) that decode before this check.
Common situations: Scripted clients joining folder + filename into the path parameter; UI bugs that pass a relative path instead of a name; probing attempts against the endpoint; copy-pasting paths from a file manager into an API call.
Related errors
- Dataset '{cleaned}' must not be a symbolic link.
- Dataset '{cleaned}' escapes the Studio datasets directory.
- dataset path may not contain null bytes
- dataset path may not contain '..' segments: {raw!r}
- save_directory may not contain null bytes
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/651ca9878e90053f.
Report an issue: GitHub.