fix(logging): scrub callback config params to prevent credential leak in Langfuse and other loggers

- Add scrub_callback_config_params_from_dict using _supported_callback_params
- Apply at litellm_logging layer so all callbacks (Langfuse, Langsmith, etc.) are protected
- Remove Langfuse-specific filter; fix is now centralized
- Add test_scrub_callback_config_params_removes_credentials
- Add before/after screenshots showing secret leak fixed

Made-with: Cursor
This commit is contained in:
Sameer Kankute 2026-03-11 13:42:57 +05:30
parent 628510d1b5
commit eacdbd19cf
6 changed files with 85 additions and 34 deletions

View file

@ -20,16 +20,16 @@ from packaging.version import Version
import litellm
from litellm._logging import verbose_logger
from litellm.constants import MAX_LANGFUSE_INITIALIZED_CLIENTS
from litellm.litellm_core_utils.core_helpers import (
safe_deep_copy,
reconstruct_model_name,
filter_exceptions_from_params,
)
from litellm.litellm_core_utils.redact_messages import redact_user_api_key_info
from litellm.integrations.langfuse.langfuse_mock_client import (
create_mock_langfuse_client,
should_use_langfuse_mock,
)
from litellm.litellm_core_utils.core_helpers import (
filter_exceptions_from_params,
reconstruct_model_name,
safe_deep_copy,
)
from litellm.litellm_core_utils.redact_messages import redact_user_api_key_info
from litellm.llms.custom_httpx.http_handler import _get_httpx_client
from litellm.secret_managers.main import str_to_bool
from litellm.types.integrations.langfuse import *
@ -272,7 +272,9 @@ class LangFuseLogger:
"""
try:
verbose_logger.debug(
f"Langfuse Logging - Enters logging function for model {kwargs}"
"Langfuse Logging - Enters logging function for model call_type=%s litellm_call_id=%s",
kwargs.get("call_type"),
kwargs.get("litellm_call_id"),
)
# set default values for input/output for langfuse logging

View file

@ -1,36 +1,60 @@
from typing import Dict, Optional
from litellm.secret_managers.main import get_secret_str
from litellm.types.utils import StandardCallbackDynamicParams
# Hardcoded list of supported callback params to avoid runtime inspection issues with TypedDict
# Callback config params - never send to external loggers (credential leak)
_SUPPORTED_CALLBACK_PARAMS_FROZEN = frozenset(
[
"langfuse_public_key",
"langfuse_secret",
"langfuse_secret_key",
"langfuse_host",
"langfuse_prompt_version",
"gcs_bucket_name",
"gcs_path_service_account",
"langsmith_api_key",
"langsmith_project",
"langsmith_base_url",
"langsmith_sampling_rate",
"langsmith_tenant_id",
"humanloop_api_key",
"arize_api_key",
"arize_space_key",
"arize_space_id",
"posthog_api_key",
"posthog_host",
"braintrust_api_key",
"braintrust_project",
"braintrust_host",
"slack_webhook_url",
"lunary_public_key",
"turn_off_message_logging",
"litellm_logging_obj",
"environment_variables",
]
)
# List form for iteration (excludes litellm_logging_obj, environment_variables - config only)
_supported_callback_params = [
"langfuse_public_key",
"langfuse_secret",
"langfuse_secret_key",
"langfuse_host",
"langfuse_prompt_version",
"gcs_bucket_name",
"gcs_path_service_account",
"langsmith_api_key",
"langsmith_project",
"langsmith_base_url",
"langsmith_sampling_rate",
"langsmith_tenant_id",
"humanloop_api_key",
"arize_api_key",
"arize_space_key",
"arize_space_id",
"posthog_api_key",
"posthog_host",
"braintrust_api_key",
"braintrust_project",
"braintrust_host",
"slack_webhook_url",
"lunary_public_key",
"turn_off_message_logging",
p for p in _SUPPORTED_CALLBACK_PARAMS_FROZEN if p not in ("litellm_logging_obj", "environment_variables")
]
def scrub_callback_config_params_from_dict(data: Dict) -> Dict:
if not data:
return data
keys_to_remove = [
k for k in data
if k in _SUPPORTED_CALLBACK_PARAMS_FROZEN
or k.endswith("_secret_key")
or k.endswith("_secret")
or k.endswith("_api_key")
]
result = {k: v for k, v in data.items() if k not in keys_to_remove}
return result
def initialize_standard_callback_dynamic_params(
kwargs: Optional[Dict] = None,
) -> StandardCallbackDynamicParams:

View file

@ -61,6 +61,9 @@ from litellm.integrations.mlflow import MlflowLogger
from litellm.integrations.sqs import SQSLogger
from litellm.litellm_core_utils.core_helpers import reconstruct_model_name
from litellm.litellm_core_utils.get_litellm_params import get_litellm_params
from litellm.litellm_core_utils.initialize_dynamic_callback_params import (
scrub_callback_config_params_from_dict,
)
from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import (
StandardBuiltInToolCostTracking,
)
@ -537,11 +540,14 @@ class Logging(LiteLLMLoggingBaseClass):
if _is_debugging_on() or self.litellm_request_debug:
verbose_logger.debug(f"self.optional_params: {self.optional_params}")
sanitized_optional_params = scrub_callback_config_params_from_dict(
dict(optional_params)
)
self.model_call_details.update(
{
"model": self.model,
"messages": self.messages,
"optional_params": self.optional_params,
"optional_params": sanitized_optional_params,
"litellm_params": self.litellm_params,
"start_time": self.start_time,
"stream": self.stream,
@ -550,7 +556,7 @@ class Logging(LiteLLMLoggingBaseClass):
"litellm_call_id": self.litellm_call_id,
"completion_start_time": self.completion_start_time,
"standard_callback_dynamic_params": self.standard_callback_dynamic_params,
**self.optional_params,
**sanitized_optional_params,
**additional_params,
}
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 KiB

View file

@ -5,6 +5,7 @@ sys.path.insert(0, os.path.abspath("../.."))
from litellm.litellm_core_utils.initialize_dynamic_callback_params import (
initialize_standard_callback_dynamic_params,
scrub_callback_config_params_from_dict,
)
@ -47,6 +48,24 @@ def test_dynamic_key_extraction_from_litellm_params_metadata():
assert params.get("langfuse_secret_key") == "sk-litellm"
def test_scrub_callback_config_params_removes_credentials():
"""Scrub removes callback config/credentials before passing to loggers."""
data = {
"messages": "[{'role': 'user', 'content': 'Hi'}]",
"langfuse_public_key": "pk-lf-xxx",
"langfuse_secret": "sk-lf-4be1b432-61f4-4771-82a2-a33d3822ad65",
"langfuse_host": "http://localhost:9999",
"litellm_logging_obj": "<Logging object>",
}
result = scrub_callback_config_params_from_dict(data)
assert "messages" in result
assert "langfuse_secret" not in result
assert "langfuse_public_key" not in result
assert "langfuse_host" not in result
assert "litellm_logging_obj" not in result
if __name__ == "__main__":
test_dynamic_key_extraction_from_metadata()
test_dynamic_key_extraction_from_litellm_params_metadata()
test_scrub_callback_config_params_removes_credentials()