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
- 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
- Or have the client send the already-resolved secret value directly in the request instead of the 'os.environ/NAME' indirection
- 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
- Configure all callback credentials server-side (config.yaml / env) before upgrading
- Grep clients for 'os.environ/' strings before deploying a newer proxy
- Never pass secrets by naming env vars in request bodies
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
- Only admins or team admins can update projects
- Cannot reassign project to a team you are not an admin of
- Unable to health check wildcard model for provider {custom_l
- No valid endpoint found for Arize, please set 'ARIZE_ENDPOIN
- LOGFIRE_TOKEN not found in environment variables
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/3df6ed53cd901d58.
Report an issue: GitHub.