From f2f1e3a0ba49ad71d1a37dca3dc1b42723ce6124 Mon Sep 17 00:00:00 2001 From: user <70670632+stuxf@users.noreply.github.com> Date: Wed, 29 Apr 2026 19:54:40 -0700 Subject: [PATCH] chore(proxy): block env callback refs in key metadata --- .../initialize_dynamic_callback_params.py | 17 +++++-- litellm/proxy/_types.py | 7 +++ litellm/proxy/litellm_pre_call_utils.py | 12 +++-- tests/proxy_unit_tests/test_proxy_utils.py | 49 +++++++++++++++---- 4 files changed, 68 insertions(+), 17 deletions(-) diff --git a/litellm/litellm_core_utils/initialize_dynamic_callback_params.py b/litellm/litellm_core_utils/initialize_dynamic_callback_params.py index 563609af1e..ffb6436f38 100644 --- a/litellm/litellm_core_utils/initialize_dynamic_callback_params.py +++ b/litellm/litellm_core_utils/initialize_dynamic_callback_params.py @@ -23,6 +23,13 @@ def _raise_env_reference_error(param: str, *, source: str) -> None: ) +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) + + # Hardcoded list of supported callback params to avoid runtime inspection issues with TypedDict _supported_callback_params = [ "langfuse_public_key", @@ -66,8 +73,9 @@ def initialize_standard_callback_dynamic_params( for param in _supported_callback_params: if param in kwargs: _param_value = kwargs.get(param) - if _is_env_reference(_param_value): - _raise_env_reference_error(param, source="request body") + validate_no_callback_env_reference( + param, _param_value, source="request body" + ) standard_callback_dynamic_params[param] = _param_value # type: ignore # 2. Fallback: check "metadata" or "litellm_params" -> "metadata" @@ -80,8 +88,9 @@ def initialize_standard_callback_dynamic_params( for param in _supported_callback_params: if param not in standard_callback_dynamic_params and param in metadata: _param_value = metadata.get(param) - if _is_env_reference(_param_value): - _raise_env_reference_error(param, source="metadata") + validate_no_callback_env_reference( + param, _param_value, source="metadata" + ) standard_callback_dynamic_params[param] = _param_value # type: ignore return standard_callback_dynamic_params diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 92c920ca59..e4eb4a06c8 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -17,6 +17,9 @@ from typing_extensions import Required, TypedDict from litellm._uuid import uuid from litellm.constants import MCP_STDIO_ALLOWED_COMMANDS +from litellm.litellm_core_utils.initialize_dynamic_callback_params import ( + validate_no_callback_env_reference, +) from litellm.types.integrations.slack_alerting import AlertType from litellm.types.llms.openai import ( AllMessageValues, @@ -1870,6 +1873,10 @@ class AddTeamCallback(LiteLLMPydanticObjectBase): ) if not isinstance(value, str): callback_vars[key] = str(value) + value = callback_vars[key] + validate_no_callback_env_reference( + key, value, source="key/team callback metadata" + ) return values diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 5804e3f8d9..c3e3c7716a 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -228,9 +228,7 @@ def convert_key_logging_metadata_to_callback( for var, value in data.callback_vars.items(): if team_callback_settings_obj.callback_vars is None: team_callback_settings_obj.callback_vars = {} - team_callback_settings_obj.callback_vars[var] = str( - litellm.utils.get_secret(value, default_value=value) or value - ) + team_callback_settings_obj.callback_vars[var] = str(value) return team_callback_settings_obj @@ -904,6 +902,14 @@ class LiteLLMProxyRequestSetup: callback_vars_dict.pop("team_id", None) callback_vars_dict.pop("success_callback", None) callback_vars_dict.pop("failure_callback", None) + callback_vars_dict = { + key: ( + litellm.utils.get_secret(value, default_value=value) or value + if isinstance(value, str) + else value + ) + for key, value in callback_vars_dict.items() + } return TeamCallbackMetadata( success_callback=team_config.get("success_callback", None), diff --git a/tests/proxy_unit_tests/test_proxy_utils.py b/tests/proxy_unit_tests/test_proxy_utils.py index feca4251a4..1fa3a021d7 100644 --- a/tests/proxy_unit_tests/test_proxy_utils.py +++ b/tests/proxy_unit_tests/test_proxy_utils.py @@ -235,18 +235,10 @@ async def test_add_key_or_team_level_spend_logs_metadata_to_request( "langfuse_host": "https://us.cloud.langfuse.com", "langfuse_public_key": "pk-lf-9636b7a6-c066", "langfuse_secret_key": "sk-lf-7cc8b620", - }, - { - "langfuse_host": "os.environ/LANGFUSE_HOST_TEMP", - "langfuse_public_key": "os.environ/LANGFUSE_PUBLIC_KEY_TEMP", - "langfuse_secret_key": "os.environ/LANGFUSE_SECRET_KEY_TEMP", - }, + } ], ) def test_dynamic_logging_metadata_key_and_team_metadata(callback_vars): - os.environ["LANGFUSE_PUBLIC_KEY_TEMP"] = "pk-lf-9636b7a6-c066" - os.environ["LANGFUSE_SECRET_KEY_TEMP"] = "sk-lf-7cc8b620" - os.environ["LANGFUSE_HOST_TEMP"] = "https://us.cloud.langfuse.com" from litellm.proxy.proxy_server import ProxyConfig proxy_config = ProxyConfig() @@ -317,6 +309,38 @@ def test_dynamic_logging_metadata_key_and_team_metadata(callback_vars): assert "os.environ" not in var +def test_dynamic_logging_metadata_rejects_env_references_from_key_metadata( + monkeypatch, +): + monkeypatch.setenv("LANGFUSE_SECRET_KEY_TEMP", "server-side-secret") + from litellm.proxy.proxy_server import ProxyConfig + + proxy_config = ProxyConfig() + user_api_key_dict = UserAPIKeyAuth( + api_key="test-key", + metadata={ + "logging": [ + { + "callback_name": "langfuse", + "callback_type": "success", + "callback_vars": { + "langfuse_secret_key": "os.environ/LANGFUSE_SECRET_KEY_TEMP", + }, + } + ] + }, + team_metadata={}, + ) + + with pytest.raises(ValueError) as exc_info: + _get_dynamic_logging_metadata( + user_api_key_dict=user_api_key_dict, proxy_config=proxy_config + ) + + assert "os.environ/" in str(exc_info.value) + assert "server-side-secret" not in str(exc_info.value) + + @pytest.mark.parametrize( "callback_vars", [ @@ -1263,11 +1287,16 @@ def test_proxy_config_state_post_init_callback_call(monkeypatch): } ) - LiteLLMProxyRequestSetup.add_team_based_callbacks_from_config( + callback_metadata = LiteLLMProxyRequestSetup.add_team_based_callbacks_from_config( team_id="test", proxy_config=pc, ) + assert callback_metadata is not None + assert callback_metadata.callback_vars is not None + assert callback_metadata.callback_vars["langfuse_public_key"] == "test_public_key" + assert callback_metadata.callback_vars["langfuse_secret"] == "test_secret_key" + config = pc.get_config_state() assert config["litellm_settings"]["default_team_settings"][0]["team_id"] == "test"