BerriAI/litellm · error · ValueError
extract_file_data does not accept bare str inputs. Pass…
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() normalizes file inputs for multimodal messages and rejects bare str inputs. The source comment gives the reason: behind the LiteLLM proxy a str arrives from an attacker-controlled HTTP form field, and interpreting it as a filesystem path would be an arbitrary file read on the proxy host - so strings are refused outright. Accepted inputs are bytes, an open file handle (anything with .read), ContentFile-like objects, a (filename, content) tuple, or a pathlib.Path (a type an HTTP form cannot fabricate).
Solutions
- Encode text content: file_content.encode("utf-8"); base64.b64decode(...) for base64 strings
- For local files pass a handle (open(path, "rb")) or a pathlib.Path - both are accepted
- Use the tuple form: ("doc.pdf", file_bytes)
- Proxy operators: keep the contract that clients upload real content, never server-side paths
Example fix
# before
messages = [{
"role": "user",
"content": [{"type": "file", "file": {"file_content": "@notes.txt"}}],
}] # str -> ValueError
# after
from pathlib import Path
messages = [{
"role": "user",
"content": [{"type": "file", "file": {"file_content": Path("notes.txt")}}],
}]
# or: {"file_content": ("notes.txt", Path("notes.txt").read_bytes())} Defensive patterns
Strategy: type-guard
Validate before calling
from pathlib import Path
def normalize_file_input(v):
if isinstance(v, str):
return v.encode("utf-8") # treat as text content, never a path
return v
msg = [{"type": "file", "file": {"file_content": normalize_file_input(file_content)}}] Type guard
from os import PathLike
def is_accepted_file_input(v: object) -> bool:
if isinstance(v, str):
return False
return isinstance(v, (bytes, PathLike, tuple)) or hasattr(v, "read") Prevention
- Never pass filesystem paths as strings through message file fields
- Use pathlib.Path or open(path, 'rb') for local uploads - both are accepted
- After litellm upgrades, grep your code for string file_content usages before deploying
When it happens
Trigger: Passing file content or a path as a plain string in a message part, e.g. {"type": "file", "file": {"file_content": "/etc/passwd"}} or a base64 str; upgrading litellm to a version with this hardening and re-running code that used to send strings.
Common situations: Version upgrade breaking callers that passed paths or raw/base64 strings (deliberate behavior change); proxy file-upload endpoints receiving form-field strings; SDK users migrating from a str-based convenience pattern to the typed contract.
Related errors
- Empty or unsafe filename
- Filename contains null byte
- Bedrock batch embedding `input` must be a string (or a…
- Callback param ' ' (from ) contains an 'os.environ/'…
- Compresr guardrail api_base
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/c28045328668431e.
Report an issue: GitHub.
Appendix: 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 77b7c6c40c)