BerriAI/litellm · error · ValueError

Empty or unsafe filename

Error message

Empty or unsafe filename

What it means

After safe_filename() strips null bytes and removes all directory components (both '/' and '\' separators via replace + rsplit), it rejects a result that is empty, '.', or '..' with this ValueError. This stops uploads whose entire filename is a path reference — for example 'folder/' or '..' — from turning into a directory write. It is the last guard before the name is used to write a temp file.

Source

Thrown at litellm/proxy/common_utils/path_utils.py:63

    Strips all directory components (both Unix and Windows separators),
    returning only the final name. Use this for uploaded file names
    before writing to disk.

    Args:
        filename: User-supplied filename (may contain path separators).

    Returns:
        The basename only, with no directory components.

    Raises:
        ValueError: If the resulting filename is empty or contains null bytes.
    """
    if "\x00" in filename:
        raise ValueError("Filename contains null byte")
    # Normalize backslash separators for cross-platform safety
    name: Final = filename.replace("\\", "/").rsplit("/", 1)[-1]
    if not name or name in (".", ".."):
        raise ValueError("Empty or unsafe filename")
    return name

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Always set an explicit, non-empty basename with the expected extension, e.g. 'convert.prompt'.
  2. Validate the filename client-side: non-empty, no separators, not '.' or '..'.
  3. When the filename derives from user input, generate a server-side safe name (for example a slug or UUID plus extension).

Example fix

# before
files = {"file": ("../", content)}
requests.post(url, files=files)

# after
files = {"file": ("convert.prompt", content)}
requests.post(url, files=files)
Defensive patterns

Strategy: validation

Validate before calling

def clean_upload_name(raw: str, fallback: str = "upload.prompt") -> str:
    name = raw.replace("\\", "/").rsplit("/", 1)[-1].strip()
    if not name or name in (".", ".."):
        return fallback
    return name

files = {"file": (clean_upload_name(user_filename), content)}

Type guard

def is_non_trivial_basename(filename: str) -> bool:
    name = filename.replace("\\", "/").rsplit("/", 1)[-1]
    return bool(name) and name not in (".", "..")

Try / catch

try:
    resp = client.post(url, files={"file": (raw_name, content)})
    resp.raise_for_status()
except HTTPError as e:
    if "unsafe filename" in e.response.text.lower():
        resp = client.post(url, files={"file": ("upload.prompt", content)})
    raise

Prevention

When it happens

Trigger: Upload a file whose filename is '..', '.', 'some/dir/' (trailing slash leaves an empty basename), or an empty string. The multipart filename header fully controls this, so any such value triggers the error on upload endpoints that use safe_filename (prompt file conversion).

Common situations: Programmatic uploads where the filename is built from user input that happens to be empty or a path fragment. HTTP clients that default to odd filenames when none is given. Security tests probing upload handling.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/6bfb66c27c148b41. Report an issue: GitHub.