BerriAI/litellm · error · ValueError
extract_file_data does not accept bare str inputs. Pass byte
Error message
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.
What it means
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).
Source
Thrown at litellm/litellm_core_utils/prompt_templates/common_utils.py:789
elif len(file_data) == 3:
filename, file_content, content_type = file_data
elif len(file_data) == 4:
filename, file_content, content_type, file_headers = file_data
elif isinstance(file_data, InMemoryFile):
filename = file_data.name
file_content = file_data
content_type = file_data.content_type
else:
file_content = file_data
# Convert content to bytes
if isinstance(file_content, str):
# Bare string inputs are rejected: when this helper runs in a proxy
# request handler the string came from an attacker-controlled form
# field, and opening it as a path is an arbitrary file read on the
# proxy host. SDK callers who want to upload from a path should
# either pass a pathlib.Path (a PathLike instance — see the branch
# below) or open the file themselves and pass the handle / bytes.
raise ValueError(
"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."
)
if isinstance(file_content, PathLike):
# PathLike (pathlib.Path) is a Python-level type that HTTP form
# values can't fabricate. Treat as a local file path for SDK
# convenience.
if filename is None:
filename = Path(file_content).name
with open(file_content, "rb") as f:
content = f.read()
elif isinstance(file_content, io.IOBase):
# If it's a file-like object
# Try to get filename from file handle if not already set
if not filename and hasattr(file_content, "name"):
filename = Path(file_content.name).nameView on GitHub (pinned to 6c2dcb801b)
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.
Example fix
// before
messages=[{'role':'user','content':[{'type':'file','file':{'file_data':'/tmp/report.pdf','format':'pdf'}}]}]
# after
from pathlib import Path
messages=[{'role':'user','content':[{'type':'file','file':{'file_data':Path('/tmp/report.pdf'),'format':'pdf'}}]}] Defensive patterns
Strategy: type-guard
Validate before calling
from pathlib import Path, PurePath
def file_input_ok(file_data) -> bool:
return isinstance(file_data, (bytes, bytearray, PurePath)) or hasattr(file_data, 'read') or (isinstance(file_data, tuple) and len(file_data) == 2) Type guard
from pathlib import PurePath
from typing import TypeGuard, Any
def is_accepted_file_input(v: Any) -> TypeGuard[bytes | PurePath | Any]:
return isinstance(v, (bytes, bytearray, PurePath)) or hasattr(v, 'read') Try / catch
try:
extracted = extract_file_data(file_data=file_data)
except ValueError as e:
if 'bare str' in str(e):
extracted = extract_file_data(file_data=Path(file_data)) # only when input is truly a trusted local path
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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}}.
Related errors
- Unsupported file content type: {type(file_content)}
- file_id and file_data are both None
- LLM Router not initialized. Ensure models added to proxy.
- Unified id does not contain {marker!r}: {file_id[:80]!r}
- LiteLLM Managed File object with id={file_id} has no file_ob
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/c28045328668431e.
Report an issue: GitHub.