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).name

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Wrap path strings in pathlib.Path: pass Path('/tmp/a.pdf') instead of the bare string.
  2. Or open the file yourself and pass the handle: open(path, 'rb').
  3. For raw content, pass bytes or a (filename, content_bytes) tuple.
  4. 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

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


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/c28045328668431e. Report an issue: GitHub.