diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 973048814f1..c87c0690807 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -380,6 +380,7 @@ class Logging(LiteLLMLoggingBaseClass): self.standard_callback_dynamic_params: StandardCallbackDynamicParams = ( self.initialize_standard_callback_dynamic_params(kwargs) ) + self._init_kwargs = kwargs # Process dynamic callbacks (after standard_callback_dynamic_params is initialized, # so team-scoped credentials are available for callback initialization) @@ -482,10 +483,12 @@ class Logging(LiteLLMLoggingBaseClass): # pass only the relevant dynamic params as custom_logger_init_args. _custom_logger_init_args: Optional[dict] = None if callback == "datadog": + # dd_* params are blocked from standard_callback_dynamic_params + # (request-level security), but team callback_vars in kwargs + # are admin-configured and trusted. + _source = self._init_kwargs or {} _custom_logger_init_args = { - k: v - for k, v in self.standard_callback_dynamic_params.items() - if k.startswith("dd_") + k: v for k, v in _source.items() if k.startswith("dd_") } callback_class = _init_custom_logger_compatible_class( diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 8cb9a11ffee..767134ef8fa 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -504,10 +504,22 @@ def _get_dynamic_logging_metadata( # Key-based callbacks ######################################################################################### if key_dynamic_logging_settings is not None: + from litellm.litellm_core_utils.initialize_dynamic_callback_params import ( + _request_blocked_callback_params, + ) + for item in key_dynamic_logging_settings: callback = _get_validated_callback_metadata(item=item, source="key-level") if callback is None: continue + # Strip params that could redirect traffic to attacker-controlled + # destinations (e.g. dd_site, dd_agent_host). Key metadata is + # user-configurable; only team-level callbacks are admin-trusted. + callback.callback_vars = { + k: v + for k, v in callback.callback_vars.items() + if k not in _request_blocked_callback_params + } callback_settings_obj = convert_key_logging_metadata_to_callback( data=callback, team_callback_settings_obj=callback_settings_obj, diff --git a/tests/test_litellm/integrations/datadog/test_datadog_team_handler.py b/tests/test_litellm/integrations/datadog/test_datadog_team_handler.py index 1ee497476d1..b70e8d6e129 100644 --- a/tests/test_litellm/integrations/datadog/test_datadog_team_handler.py +++ b/tests/test_litellm/integrations/datadog/test_datadog_team_handler.py @@ -192,3 +192,55 @@ class TestStandardCallbackDynamicParamsIncludesDatadog: assert "dd_site" in annotations assert "dd_agent_host" in annotations assert "dd_agent_port" in annotations + + +class TestTeamCallbackFlowPassesDDCredentials: + """ + Integration test for the full team callback flow. + + Verifies that DD credentials configured via team callback_vars + actually reach DataDogHandler when a request is processed. + The dd_* params are in _request_blocked_callback_params (to prevent + request-level injection), but team callback_vars are admin-configured + and must pass through. + """ + + def test_process_dynamic_callbacks_passes_dd_params_from_kwargs(self): + """ + Simulates the Logging.__init__ flow where team callback_vars + (dd_api_key, dd_site) are unpacked into kwargs. Verifies that + _process_dynamic_callback_list picks them up and passes them + to DataDogHandler despite _request_blocked_callback_params. + """ + from litellm.litellm_core_utils.litellm_logging import Logging + + kwargs = { + "dd_api_key": "team-dd-key-123", + "dd_site": "us5.datadoghq.com", + "model": "gpt-4", + "litellm_params": {"metadata": {}}, + } + + with patch("asyncio.create_task"): + logging_obj = Logging( + model="gpt-4", + messages=[{"role": "user", "content": "hi"}], + stream=False, + call_type="completion", + start_time="2026-01-01", + litellm_call_id="test-call-id", + function_id="test-func", + dynamic_success_callbacks=["datadog"], + kwargs=kwargs, + ) + + dd_loggers = [ + cb + for cb in (logging_obj.dynamic_success_callbacks or []) + if isinstance(cb, DataDogLogger) + ] + assert ( + len(dd_loggers) == 1 + ), "DataDogLogger should be initialized from team callback_vars" + assert dd_loggers[0].DD_API_KEY == "team-dd-key-123" + assert "us5.datadoghq.com" in dd_loggers[0].intake_url