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
- Remove the 'os.environ/' reference from the request body and send the literal, already-resolved secret value for the callback param.
- 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).
- If per-team callback creds are needed, use the proxy's team/key-level callback settings rather than raw request-body env pointers.
- 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
- Never put 'os.environ/' markers in request bodies; that syntax belongs to server-side config.yaml only.
- Resolve secrets client-side (os.getenv) before adding them to metadata.
- Configure shared callback credentials once in the proxy's litellm_settings/general_settings.
- Add a client-side linter/test asserting no request payload contains 'os.environ/'.
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
- Internal Error: Cache cannot be empty -…
- LANGTRACE_API_KEY not found in environment variables
- litellm_settings.callbacks entry
- litellm_settings.callbacks entry
- LOGFIRE_TOKEN not found in environment variables
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)