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

  1. Pass only the bare filename (single component, no directories): GET /training/diffusion/dataset/myset/image/cat.png — never a path.
  2. Fix the client to use Path(filename).name (or basename) before filling the URL parameter.
  3. 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

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


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/651ca9878e90053f. Report an issue: GitHub.