{"record":{"id":"6bfb66c27c148b41","repo":"BerriAI/litellm","slug":"empty-or-unsafe-filename","errorCode":null,"errorMessage":"Empty or unsafe filename","messagePattern":"Empty or unsafe filename","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/common_utils/path_utils.py","lineNumber":63,"sourceCode":"    Strips all directory components (both Unix and Windows separators),\n    returning only the final name. Use this for uploaded file names\n    before writing to disk.\n\n    Args:\n        filename: User-supplied filename (may contain path separators).\n\n    Returns:\n        The basename only, with no directory components.\n\n    Raises:\n        ValueError: If the resulting filename is empty or contains null bytes.\n    \"\"\"\n    if \"\\x00\" in filename:\n        raise ValueError(\"Filename contains null byte\")\n    # Normalize backslash separators for cross-platform safety\n    name: Final = filename.replace(\"\\\\\", \"/\").rsplit(\"/\", 1)[-1]\n    if not name or name in (\".\", \"..\"):\n        raise ValueError(\"Empty or unsafe filename\")\n    return name\n","sourceCodeStart":45,"sourceCodeEnd":65,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/common_utils/path_utils.py#L45-L65","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Always set an explicit, non-empty basename with the expected extension, e.g. 'convert.prompt'.","Validate the filename client-side: non-empty, no separators, not '.' or '..'.","When the filename derives from user input, generate a server-side safe name (for example a slug or UUID plus extension)."],"exampleFix":"# before\nfiles = {\"file\": (\"../\", content)}\nrequests.post(url, files=files)\n\n# after\nfiles = {\"file\": (\"convert.prompt\", content)}\nrequests.post(url, files=files)","handlingStrategy":"validation","validationCode":"def clean_upload_name(raw: str, fallback: str = \"upload.prompt\") -> str:\n    name = raw.replace(\"\\\\\", \"/\").rsplit(\"/\", 1)[-1].strip()\n    if not name or name in (\".\", \"..\"):\n        return fallback\n    return name\n\nfiles = {\"file\": (clean_upload_name(user_filename), content)}","typeGuard":"def is_non_trivial_basename(filename: str) -> bool:\n    name = filename.replace(\"\\\\\", \"/\").rsplit(\"/\", 1)[-1]\n    return bool(name) and name not in (\".\", \"..\")","tryCatchPattern":"try:\n    resp = client.post(url, files={\"file\": (raw_name, content)})\n    resp.raise_for_status()\nexcept HTTPError as e:\n    if \"unsafe filename\" in e.response.text.lower():\n        resp = client.post(url, files={\"file\": (\"upload.prompt\", content)})\n    raise","preventionTips":["Always set an explicit filename with a real basename and the expected extension in multipart uploads.","Validate filenames from user input: non-empty, no separators, not '.' or '..'.","Prefer server-generated names for stored uploads; keep the original name in metadata only."],"tags":["litellm","security","file-upload","path-validation","filename","prompts"],"backgroundTag":"path-traversal-blocked","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}