{"record":{"id":"c28045328668431e","repo":"BerriAI/litellm","slug":"extract-file-data-does-not-accept-bare-str-inputs","errorCode":null,"errorMessage":"extract_file_data does not accept bare str inputs. Pass bytes, an open file handle, a (filename, content) tuple, or a pathlib.Path. To upload a local file from a path, call open(path, 'rb') yourself.","messagePattern":"extract_file_data does not accept bare str inputs\\. Pass bytes, an open file handle, a \\(filename, content\\) tuple, or a pathlib\\.Path\\. To upload a local file from a path, call open\\(path, 'rb'\\) yourself\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/prompt_templates/common_utils.py","lineNumber":789,"sourceCode":"        elif len(file_data) == 3:\n            filename, file_content, content_type = file_data\n        elif len(file_data) == 4:\n            filename, file_content, content_type, file_headers = file_data\n    elif isinstance(file_data, InMemoryFile):\n        filename = file_data.name\n        file_content = file_data\n        content_type = file_data.content_type\n    else:\n        file_content = file_data\n    # Convert content to bytes\n    if isinstance(file_content, str):\n        # Bare string inputs are rejected: when this helper runs in a proxy\n        # request handler the string came from an attacker-controlled form\n        # field, and opening it as a path is an arbitrary file read on the\n        # proxy host. SDK callers who want to upload from a path should\n        # either pass a pathlib.Path (a PathLike instance — see the branch\n        # below) or open the file themselves and pass the handle / bytes.\n        raise ValueError(\n            \"extract_file_data does not accept bare str inputs. Pass bytes, \"\n            \"an open file handle, a (filename, content) tuple, or a \"\n            \"pathlib.Path. To upload a local file from a path, call \"\n            \"open(path, 'rb') yourself.\"\n        )\n    if isinstance(file_content, PathLike):\n        # PathLike (pathlib.Path) is a Python-level type that HTTP form\n        # values can't fabricate. Treat as a local file path for SDK\n        # convenience.\n        if filename is None:\n            filename = Path(file_content).name\n        with open(file_content, \"rb\") as f:\n            content = f.read()\n    elif isinstance(file_content, io.IOBase):\n        # If it's a file-like object\n        # Try to get filename from file handle if not already set\n        if not filename and hasattr(file_content, \"name\"):\n            filename = Path(file_content.name).name","sourceCodeStart":771,"sourceCodeEnd":807,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/prompt_templates/common_utils.py#L771-L807","documentation":"extract_file_data rejects bare str file inputs with ValueError. The comment in the source explains why: when litellm runs as a proxy, a string from this code path originates from an attacker-controlled HTTP form field, and opening it as a filesystem path would be an arbitrary file read on the proxy host. Callers must pass bytes, an open file handle, a (filename, content) tuple, or a pathlib.Path (PathLike is safe because HTTP forms cannot fabricate it).","triggerScenarios":"Sending a file block in messages where file_data is a plain string (e.g. a filename or data-URL string) through the proxy; SDK code passing a path string like '/tmp/a.pdf' as file content; upgrading from an older litellm that accepted str paths.","commonSituations":"Version migration: older litellm versions opened str paths, so existing code breaks with the new guard; proxy deployments where the security fix matters most; prompt templates constructing {'type':'file','file':{'file_data': some_string}}.","solutions":["Wrap path strings in pathlib.Path: pass Path('/tmp/a.pdf') instead of the bare string.","Or open the file yourself and pass the handle: open(path, 'rb').","For raw content, pass bytes or a (filename, content_bytes) tuple.","For base64/data URLs, keep them in file_id (the URL field), not file_data as a bare str path."],"exampleFix":"// before\nmessages=[{'role':'user','content':[{'type':'file','file':{'file_data':'/tmp/report.pdf','format':'pdf'}}]}]\n\n# after\nfrom pathlib import Path\nmessages=[{'role':'user','content':[{'type':'file','file':{'file_data':Path('/tmp/report.pdf'),'format':'pdf'}}]}]","handlingStrategy":"type-guard","validationCode":"from pathlib import Path, PurePath\n\ndef file_input_ok(file_data) -> bool:\n    return isinstance(file_data, (bytes, bytearray, PurePath)) or hasattr(file_data, 'read') or (isinstance(file_data, tuple) and len(file_data) == 2)","typeGuard":"from pathlib import PurePath\nfrom typing import TypeGuard, Any\n\ndef is_accepted_file_input(v: Any) -> TypeGuard[bytes | PurePath | Any]:\n    return isinstance(v, (bytes, bytearray, PurePath)) or hasattr(v, 'read')","tryCatchPattern":"try:\n    extracted = extract_file_data(file_data=file_data)\nexcept ValueError as e:\n    if 'bare str' in str(e):\n        extracted = extract_file_data(file_data=Path(file_data))  # only when input is truly a trusted local path\n    else:\n        raise","preventionTips":["Never pass path strings; always pathlib.Path for local files.","In proxy code, treat any str file payload as untrusted input.","After litellm upgrades, grep for file_data usages that relied on the old str-path behavior."],"tags":["files","security","input-validation","migration"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}