BerriAI/litellm · error · ValueError

Callback param '{param}' (from {source}) contains an 'os.env

Error message

Callback param '{param}' (from {source}) contains an 'os.environ/' reference. Environment references in request-supplied parameters are no longer resolved server-side for security reasons.\nTo resolve:\n  1. Remove the 'os.environ/' reference from your request body / metadata.\n  2. Either (a) configure this callback value in your proxy config.yaml under 'litellm_settings' / 'general_settings', or (b) pass the resolved secret value directly in the request.\nSee https://docs.litellm.ai/docs/proxy/logging for server-side callback configuration.

What it means

A deliberate security hardening error: LiteLLM no longer resolves 'os.environ/...' references found in request-supplied callback parameters (e.g. metadata like langfuse_secret_key sent in the request body). Resolving env refs server-side from untrusted request bodies lets any caller exfiltrate arbitrary environment variables (secrets) through callback configs, so the value is now rejected instead of expanded.

Source

Thrown at litellm/litellm_core_utils/initialize_dynamic_callback_params.py:28

    kwargs: dict[str, Any],
) -> Iterator[tuple[str, dict[str, Any]]]:
    litellm_params: Final = kwargs.get("litellm_params")
    if isinstance(litellm_params, dict):
        nested: Final = litellm_params.get("metadata")
        if isinstance(nested, dict):
            yield "litellm_params.metadata", nested
    for key in _CLIENT_CALLBACK_METADATA_SLOTS:
        candidate = kwargs.get(key)
        if isinstance(candidate, dict):
            yield key, candidate


def _is_env_reference(value: object) -> bool:
    return isinstance(value, str) and "os.environ/" in value


def _raise_env_reference_error(param: str, *, source: str) -> None:
    raise ValueError(
        f"Callback param '{param}' (from {source}) contains an 'os.environ/' "
        "reference. Environment references in request-supplied parameters are "
        "no longer resolved server-side for security reasons.\n"
        "To resolve:\n"
        "  1. Remove the 'os.environ/' reference from your request body / "
        "metadata.\n"
        "  2. Either (a) configure this callback value in your proxy "
        "config.yaml under 'litellm_settings' / 'general_settings', or "
        "(b) pass the resolved secret value directly in the request.\n"
        "See https://docs.litellm.ai/docs/proxy/logging for server-side "
        "callback configuration."
    )


def validate_no_callback_env_reference(param: str, value: object, *, source: str) -> None:
    if _is_env_reference(value):
        _raise_env_reference_error(param, source=source)

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Move callback credentials to the proxy config.yaml under litellm_settings (callbacks + their env vars) or general_settings, and let the server resolve its own environment
  2. Or have the client send the already-resolved secret value directly in the request instead of the 'os.environ/NAME' indirection
  3. Remove any 'os.environ/' strings from request bodies/metadata; scan client code for the literal 'os.environ/' before upgrading

Example fix

# before (request body)
curl -X POST /v1/chat/completions -d '{
  "model": "gpt-4o",
  "messages": [...],
  "metadata": {"langfuse_secret_key": "os.environ/LANGFUSE_KEY"}
}'

# after (config.yaml — server-side resolution)
litellm_settings:
  callbacks: ["langfuse"]
# langfuse env vars set in the proxy's environment: LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY
Defensive patterns

Strategy: validation

Validate before calling

def assert_no_env_refs(payload: dict) -> None:
    def walk(node):
        if isinstance(node, dict):
            for v in node.values(): walk(v)
        elif isinstance(node, list):
            for v in node: walk(v)
        elif isinstance(node, str) and 'os.environ/' in node:
            raise ValueError(f"os.environ/ reference in request payload: {node!r}")
    walk(payload)

assert_no_env_refs(request_body)  # call before litellm.acompletion(**request_body)

Try / catch

try:
    litellm.acompletion(**params)
except ValueError as e:
    if 'os.environ/' in str(e) and 'no longer resolved server-side' in str(e):
        params['metadata'].pop('langfuse_secret_key')  # move to config.yaml
        litellm.acompletion(**params)
    else:
        raise

Prevention

When it happens

Trigger: Sending a chat/completions (or embeddings) request whose litellm_params.metadata or callback-related fields contain a string with 'os.environ/' in it — e.g. metadata: {"langfuse_secret_key": "os.environ/LANGFUSE_KEY"} — on a LiteLLM version that includes initialize_dynamic_callback_params.py.

Common situations: Upgrading LiteLLM/proxy to a version containing this security change while client code still injects per-request callback credentials via os.environ references; migrating from the old documented per-request metadata pattern for langfuse/langsmith keys.

Related errors


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