raise ValueError on os.environ/ references in request-supplied callback params

Previously these were silently dropped with a verbose warning, which
could break observability integrations without surfacing a clear error.
Now raises ValueError with remediation steps (configure server-side
or pass the resolved value) so callers get immediate, actionable feedback.
This commit is contained in:
Yuneng Jiang
2026-04-13 12:00:25 -07:00
parent 26c35aa8eb
commit df75e79615
2 changed files with 118 additions and 16 deletions
@@ -1,12 +1,28 @@
from typing import Dict, Optional
from litellm._logging import verbose_logger
from litellm.types.utils import StandardCallbackDynamicParams
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."
)
# Hardcoded list of supported callback params to avoid runtime inspection issues with TypedDict
_supported_callback_params = [
"langfuse_public_key",
@@ -52,13 +68,7 @@ def initialize_standard_callback_dynamic_params(
if param in kwargs:
_param_value = kwargs.get(param)
if _is_env_reference(_param_value):
verbose_logger.warning(
"Dropping callback param '%s': os.environ/ references "
"in request-supplied parameters are not resolved. "
"Configure this value server-side instead.",
param,
)
continue
_raise_env_reference_error(param, source="request body")
standard_callback_dynamic_params[param] = _param_value # type: ignore
# 2. Fallback: check "metadata" or "litellm_params" -> "metadata"
@@ -72,14 +82,7 @@ def initialize_standard_callback_dynamic_params(
if param not in standard_callback_dynamic_params and param in metadata:
_param_value = metadata.get(param)
if _is_env_reference(_param_value):
verbose_logger.warning(
"Dropping callback param '%s' from metadata: "
"os.environ/ references in request-supplied "
"parameters are not resolved. Configure this "
"value server-side instead.",
param,
)
continue
_raise_env_reference_error(param, source="metadata")
standard_callback_dynamic_params[param] = _param_value # type: ignore
return standard_callback_dynamic_params
@@ -0,0 +1,99 @@
import os
import sys
import pytest
sys.path.insert(0, os.path.abspath("../../.."))
from litellm.litellm_core_utils.initialize_dynamic_callback_params import (
initialize_standard_callback_dynamic_params,
)
def test_resolves_plain_values_at_top_level():
kwargs = {
"langfuse_public_key": "pk-test",
"langfuse_secret_key": "sk-test",
}
params = initialize_standard_callback_dynamic_params(kwargs)
assert params.get("langfuse_public_key") == "pk-test"
assert params.get("langfuse_secret_key") == "sk-test"
def test_resolves_plain_values_from_metadata():
kwargs = {
"metadata": {
"langfuse_public_key": "pk-meta",
"langfuse_host": "https://test.langfuse.com",
}
}
params = initialize_standard_callback_dynamic_params(kwargs)
assert params.get("langfuse_public_key") == "pk-meta"
assert params.get("langfuse_host") == "https://test.langfuse.com"
def test_env_reference_at_top_level_raises_with_guidance():
kwargs = {"langfuse_public_key": "os.environ/LANGFUSE_PUBLIC_KEY"}
with pytest.raises(ValueError) as exc_info:
initialize_standard_callback_dynamic_params(kwargs)
message = str(exc_info.value)
assert "langfuse_public_key" in message
assert "request body" in message
assert "os.environ/" in message
assert "config.yaml" in message
def test_env_reference_in_metadata_raises_with_guidance():
kwargs = {
"metadata": {
"langsmith_api_key": "os.environ/LANGSMITH_API_KEY",
}
}
with pytest.raises(ValueError) as exc_info:
initialize_standard_callback_dynamic_params(kwargs)
message = str(exc_info.value)
assert "langsmith_api_key" in message
assert "metadata" in message
def test_env_reference_in_litellm_params_metadata_raises():
kwargs = {
"litellm_params": {
"metadata": {
"gcs_bucket_name": "os.environ/GCS_BUCKET",
}
}
}
with pytest.raises(ValueError) as exc_info:
initialize_standard_callback_dynamic_params(kwargs)
assert "gcs_bucket_name" in str(exc_info.value)
def test_non_string_values_are_not_flagged():
kwargs = {
"langsmith_sampling_rate": 0.5,
"turn_off_message_logging": True,
}
params = initialize_standard_callback_dynamic_params(kwargs)
assert params.get("langsmith_sampling_rate") == 0.5
assert params.get("turn_off_message_logging") is True
def test_empty_kwargs_returns_empty_params():
params = initialize_standard_callback_dynamic_params(None)
assert dict(params) == {}
params = initialize_standard_callback_dynamic_params({})
assert dict(params) == {}