From 43054a239059cbc695a0f0215aedfa15615750cc Mon Sep 17 00:00:00 2001 From: Harshit Jain Date: Thu, 26 Feb 2026 19:03:49 +0530 Subject: [PATCH 1/2] fix: langfuse trace leak key on model params --- litellm/integrations/langfuse/langfuse.py | 31 +++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/litellm/integrations/langfuse/langfuse.py b/litellm/integrations/langfuse/langfuse.py index 7bf97665fd..e2db8be045 100644 --- a/litellm/integrations/langfuse/langfuse.py +++ b/litellm/integrations/langfuse/langfuse.py @@ -25,6 +25,7 @@ from litellm.litellm_core_utils.core_helpers import ( reconstruct_model_name, filter_exceptions_from_params, ) +from litellm.litellm_core_utils.model_param_helper import ModelParamHelper 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, @@ -123,7 +124,7 @@ class LangFuseLogger: self.langfuse_flush_interval = LangFuseLogger._get_langfuse_flush_interval( flush_interval ) - + if should_use_langfuse_mock(): self.langfuse_client = create_mock_langfuse_client() self.is_mock_mode = True @@ -291,8 +292,6 @@ class LangFuseLogger: functions = optional_params.pop("functions", None) tools = optional_params.pop("tools", None) - # Remove secret_fields to prevent leaking sensitive data (e.g., authorization headers) - optional_params.pop("secret_fields", None) if functions is not None: prompt["functions"] = functions if tools is not None: @@ -505,13 +504,18 @@ class LangFuseLogger: kwargs.get("model", ""), custom_llm_provider, metadata ) + # Use whitelisted model parameters to prevent leaking secrets + sanitized_model_params = ModelParamHelper.get_standard_logging_model_parameters( + optional_params + ) + trace.generation( CreateGeneration( name=metadata.get("generation_name", "litellm-completion"), startTime=start_time, endTime=end_time, model=model_name, - modelParameters=optional_params, + modelParameters=sanitized_model_params, prompt=input, completion=output, usage={ @@ -607,9 +611,7 @@ class LangFuseLogger: # Use standard_logging_object.trace_id if available (when trace_id from metadata is None) # This allows standard trace_id to be used when provided in standard_logging_object if trace_id is None and standard_logging_object is not None: - trace_id = cast( - Optional[str], standard_logging_object.get("trace_id") - ) + trace_id = cast(Optional[str], standard_logging_object.get("trace_id")) # Fallback to litellm_call_id if no trace_id found if trace_id is None: trace_id = litellm_call_id @@ -833,13 +835,26 @@ class LangFuseLogger: kwargs.get("model", ""), custom_llm_provider, metadata ) + # Use whitelisted model_parameters from StandardLoggingPayload + # to prevent leaking secrets (api_key, auth headers, etc.) + if standard_logging_object is not None: + sanitized_model_params = standard_logging_object.get( + "model_parameters", optional_params + ) + else: + sanitized_model_params = ( + ModelParamHelper.get_standard_logging_model_parameters( + optional_params + ) + ) + generation_params = { "name": generation_name, "id": clean_metadata.pop("generation_id", generation_id), "start_time": start_time, "end_time": end_time, "model": model_name, - "model_parameters": optional_params, + "model_parameters": sanitized_model_params, "input": input if not mask_input else "redacted-by-litellm", "output": output if not mask_output else "redacted-by-litellm", "usage": usage, From 20bf3aa8070a4dc150bb8edaddb6bd3306b83a53 Mon Sep 17 00:00:00 2001 From: Harshit28j Date: Tue, 10 Mar 2026 17:16:46 +0530 Subject: [PATCH 2/2] fix: pop sensitive keys from langfuse --- litellm/litellm_core_utils/litellm_logging.py | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index e450b233c7..73a8b92c1b 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -1653,9 +1653,7 @@ class Logging(LiteLLMLoggingBaseClass): self.model_call_details[ "standard_logging_object" - ] = self._build_standard_logging_payload( - logging_result, start_time, end_time - ) + ] = self._build_standard_logging_payload(logging_result, start_time, end_time) if ( standard_logging_payload := self.model_call_details.get( @@ -2518,9 +2516,7 @@ class Logging(LiteLLMLoggingBaseClass): ## STANDARDIZED LOGGING PAYLOAD self.model_call_details[ "standard_logging_object" - ] = self._build_standard_logging_payload( - result, start_time, end_time - ) + ] = self._build_standard_logging_payload(result, start_time, end_time) # print standard logging payload if ( @@ -4195,8 +4191,7 @@ def _maybe_auto_initialize_arize_phoenix(_in_memory_loggers: list) -> None: litellm.logging_callback_manager.add_litellm_callback(phoenix_logger) verbose_logger.info( - "Auto-initialized Arize Phoenix logger alongside otel " - "(endpoint=%s)", + "Auto-initialized Arize Phoenix logger alongside otel " "(endpoint=%s)", arize_phoenix_config.endpoint, ) except Exception as e: @@ -4755,9 +4750,11 @@ class StandardLoggingPayloadSetup: ).model_dump() if isinstance(_raw, dict): if ResponseAPILoggingUtils._is_response_api_usage(_raw): - return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( - _raw - ).model_dump() + return ( + ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( + _raw + ).model_dump() + ) return _raw if isinstance(_raw, Usage): return _raw.model_dump() @@ -5482,21 +5479,23 @@ def scrub_sensitive_keys_in_metadata(litellm_params: Optional[dict]): litellm_params["_langfuse_masking_function"] = masking_fn litellm_params["metadata"] = metadata - ## check user_api_key_metadata for sensitive logging keys - cleaned_user_api_key_metadata = {} - if "user_api_key_metadata" in metadata and isinstance( - metadata["user_api_key_metadata"], dict - ): - for k, v in metadata["user_api_key_metadata"].items(): - if k == "logging": # prevent logging user logging keys - cleaned_user_api_key_metadata[ - k - ] = "scrubbed_by_litellm_for_sensitive_keys" - else: - cleaned_user_api_key_metadata[k] = v + ## remove sensitive logging/callback keys from metadata dicts + ## these contain credentials (langfuse_secret_key, langfuse_public_key, etc.) + _sensitive_keys = {"logging", "callback_settings"} - metadata["user_api_key_metadata"] = cleaned_user_api_key_metadata - litellm_params["metadata"] = metadata + for metadata_field in ( + "user_api_key_metadata", + "user_api_key_auth_metadata", + "user_api_key_team_metadata", + ): + if metadata_field in metadata and isinstance(metadata[metadata_field], dict): + for sensitive_key in _sensitive_keys: + metadata[metadata_field].pop(sensitive_key, None) + + ## remove user_api_key_auth entirely - contains full auth object with nested credentials + metadata.pop("user_api_key_auth", None) + + litellm_params["metadata"] = metadata return litellm_params @@ -5603,4 +5602,3 @@ def create_dummy_standard_logging_payload() -> StandardLoggingPayload: model_parameters={"stream": True}, hidden_params=hidden_params, ) -