fix(datadog): read team callback dd_* params from kwargs instead of blocked dynamic params (#35115)
Some checks failed
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled

Team-scoped DD credentials (dd_api_key, dd_site) set via POST /team/{id}/callback were silently dropped because _request_blocked_callback_params blocks them from standard_callback_dynamic_params. The security block is correct for request-level injection, but team callback_vars are admin-configured and trusted.

Store the raw init kwargs on the Logging instance and read dd_* params from there in _process_dynamic_callback_list instead of from standard_callback_dynamic_params.

Adds an integration test that exercises the full Logging.__init__ flow with team callback_vars to prevent regression.
This commit is contained in:
Aanchal Khandelwal 2026-07-30 17:37:05 -04:00 committed by GitHub
parent ac7c2dc0d7
commit a5164fe210
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 70 additions and 3 deletions

View file

@ -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(

View file

@ -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,

View file

@ -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