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
- Send a clean filename: basename only, extension '.prompt', no control characters.
- Sanitize upstream: reject or strip \x00 from any filename before forwarding the upload.
- 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
- Generate upload filenames server-side (slug or UUID plus extension) instead of passing user input.
- Strip control characters from filenames at the boundary where user input enters.
- Add a client-side filename lint step in test suites that upload files.
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
- Empty or unsafe filename
- extract_file_data does not accept bare str inputs. Pass byte
- Invalid identifier {identifier!r}: contains disallowed chara
- Invalid identifier {identifier!r}: path traversal detected
- Callback param '{param}' (from {source}) contains an 'os.env
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/8188c993b57c7953.
Report an issue: GitHub.