BerriAI/litellm · error · ValueError

Callback param ' ' (from ) contains an 'os.environ/'…

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

Security-hardening ValueError raised by initialize_standard_callback_dynamic_params(): request-supplied values for callback parameters (langfuse_secret, langsmith_api_key, slack_webhook_url, etc., whether top-level or inside metadata/litellm_params.metadata) must not contain the marker 'os.environ/'. Older LiteLLM resolved such references against the server's environment, letting any client read arbitrary server env vars, so the proxy now rejects them outright.

Solutions

  1. Remove the 'os.environ/' reference from the request body and send the literal, already-resolved secret value for the callback param.
  2. Move the callback credential to server-side configuration: config.yaml litellm_settings/general_settings (env refs in config.yaml are still resolved at startup, on the trusted host).
  3. If per-team callback creds are needed, use the proxy's team/key-level callback settings rather than raw request-body env pointers.
  4. Rotate any secrets that were previously injectable via client requests.

Example fix

# before
client.chat.completions.create(
    model='gpt-4o', messages=messages,
    extra_body={'metadata': {'langfuse_secret': 'os.environ/LANGFUSE_SECRET'}},
)

# after
client.chat.completions.create(
    model='gpt-4o', messages=messages,
    extra_body={'metadata': {'langfuse_secret': 'pk-lf-actual-value'}},
)
Defensive patterns

Strategy: validation

Validate before calling

def sanitize_callback_params(payload: dict) -> dict:
    blocked = {'langfuse_public_key','langfuse_secret','langfuse_secret_key','langfuse_host','langsmith_api_key','slack_webhook_url','langfuse_prompt_version','arize_api_key','arize_space_key','arize_space_id','posthog_api_key','posthog_host','braintrust_api_key','braintrust_project','braintrust_host','lunary_public_key','humanloop_api_key','turn_off_message_logging','langsmith_base_url','langsmith_project','langsmith_sampling_rate','langsmith_tenant_id'}
    meta = payload.setdefault('metadata', {})
    for k in list(meta):
        if k in blocked and isinstance(meta[k], str) and 'os.environ/' in meta[k]:
            del meta[k]  # or substitute the resolved value client-side
    for k in list(payload):
        if k in blocked and isinstance(payload[k], str) and 'os.environ/' in payload[k]:
            del payload[k]
    return payload

payload = sanitize_callback_params({'model': 'gpt-4o', 'messages': [...], 'metadata': {'langfuse_secret': 'os.environ/LANGFUSE_SECRET'}})
resp = client.chat.completions.create(**payload)

Type guard

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

Try / catch

try:
    resp = client.chat.completions.create(**payload)
except openai.BadRequestError as e:
    if 'os.environ/' in str(e) and 'Callback param' in str(e):
        # strip env references from the request body and send resolved values instead
        raise
    raise

Prevention

When it happens

Trigger: POSTing to /chat/completions (or /v1/chat/completions) with extra_body/metadata like {'metadata': {'langfuse_secret': 'os.environ/LANGFUSE_SECRET'}} or a top-level 'langsmith_api_key': 'os.environ/LANGSMITH_API_KEY' — any supported callback param whose string value contains 'os.environ/'.

Common situations: Upgrading the LiteLLM proxy after the security fix while clients still send os.environ/ pointers that used to be resolved; copying config.yaml examples (where os.environ/ is valid) into request bodies; multi-tenant setups where each team passed env-var pointers for their observability keys.

Related errors


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

Appendix: 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 77b7c6c40c)