BerriAI/litellm · error · ValueError

Filename contains null byte

Error message

Filename contains null byte

What it means

safe_filename() takes a user-supplied filename (typically from an upload form) before writing it to disk. It first rejects any filename containing a NUL byte with this ValueError. The guard exists because NUL bytes cannot appear in real filenames and can confuse lower-level file APIs. The prompt-file conversion endpoint (prompt_endpoints.py) runs uploaded .prompt filenames through this function.

Source

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

def safe_filename(filename: str) -> str:
    """
    Extract a safe filename from a user-supplied path.

    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. Send a clean filename: basename only, extension '.prompt', no control characters.
  2. Sanitize upstream: reject or strip \x00 from any filename before forwarding the upload.
  3. Call a basename + control-character filter in client code when filenames come from end users.

Example fix

# before
files = {"file": ("idea.prompt\x00.txt", content)}
requests.post(url, files=files)

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

Strategy: validation

Validate before calling

def safe_upload_name(filename: str) -> str | None:
    if not filename or "\x00" in filename:
        return None
    name = filename.replace("\\", "/").rsplit("/", 1)[-1]
    return name if name and name not in (".", "..") else None

clean = safe_upload_name(user_filename)
assert clean and clean.endswith(".prompt"), "bad upload filename"

Type guard

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

Try / catch

try:
    resp = client.post(url, files={"file": (user_filename, content)})
    resp.raise_for_status()
except HTTPError as e:
    if e.response.status_code in (400, 500) and "null byte" in e.response.text.lower():
        resp = client.post(url, files={"file": (safe_upload_name(user_filename), content)})
    raise

Prevention

When it happens

Trigger: POST a file upload (for example to the /prompts dotprompt conversion route) with a filename field containing \x00, such as 'my.prompt\x00.txt'. The filename comes from the multipart part header, so it is fully client-controlled.

Common situations: A hostile or fuzzed upload carries control characters in the filename. A client copies a filename from untrusted metadata without sanitizing it. A proxy bug injects stray bytes into part headers.

Related errors


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