diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index ebb7a5ba1d7..e69c0c80420 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -1095,16 +1095,14 @@ def _sanitize_request_body_for_spend_logs_payload( request_body: Mapping[str, object], visited: set | None = None, max_string_length_prompt_in_db: int | None = None, - redact_credentials: bool = False, ) -> dict: """ Recursively sanitize request body to prevent logging large base64 strings or other large values. Truncates strings longer than MAX_STRING_LENGTH_PROMPT_IN_DB characters and handles nested dictionaries. At every nesting level, also strips keys listed in _SENSITIVE_REQUEST_BODY_KEYS (e.g. secret_fields, - which holds raw HTTP headers including Authorization tokens). With ``redact_credentials``, string - values under keys SensitiveDataMasker classifies as credentials are replaced with - REDACTED_BY_LITELM_STRING. + which holds raw HTTP headers including Authorization tokens), and replaces string values under keys + SensitiveDataMasker classifies as credentials with REDACTED_BY_LITELM_STRING. """ from litellm.constants import ( LITELLM_TRUNCATED_PAYLOAD_FIELD, @@ -1124,9 +1122,7 @@ def _sanitize_request_body_for_spend_logs_payload( def _sanitize_value(value: object) -> object: if isinstance(value, Mapping): - return _sanitize_request_body_for_spend_logs_payload( - value, visited, max_string_length_prompt_in_db, redact_credentials - ) + return _sanitize_request_body_for_spend_logs_payload(value, visited, max_string_length_prompt_in_db) elif isinstance(value, list): return [_sanitize_value(item) for item in value] elif isinstance(value, str): @@ -1164,7 +1160,7 @@ def _sanitize_request_body_for_spend_logs_payload( return value return { - k: REDACTED_BY_LITELM_STRING if redact_credentials and _is_request_body_credential(k, v) else _sanitize_value(v) + k: REDACTED_BY_LITELM_STRING if _is_request_body_credential(k, v) else _sanitize_value(v) for k, v in request_body.items() if k not in _SENSITIVE_REQUEST_BODY_KEYS } @@ -1582,7 +1578,7 @@ def _get_proxy_server_request_for_spend_logs_payload( _request_body = _convert_mapping_to_json_serializable(without_classifier_audit(_request_body)) perform_redaction(model_call_details=_request_body, result=None) - _request_body = _sanitize_request_body_for_spend_logs_payload(_request_body, redact_credentials=True) + _request_body = _sanitize_request_body_for_spend_logs_payload(_request_body) _request_body_json_str: Final = safe_dumps(_request_body) if LITELLM_TRUNCATED_PAYLOAD_FIELD in _request_body_json_str: verbose_proxy_logger.info( diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py index 9128c384b8c..f3991c0e494 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py @@ -612,7 +612,7 @@ def test_sanitize_request_body_for_spend_logs_payload_mixed_types(): request_body = { "text": long_string, "number": 42, - "nested": {"list": ["short", long_string], "dict": {"key": long_string}}, + "nested": {"list": ["short", long_string], "dict": {"value": long_string}}, } sanitized = _sanitize_request_body_for_spend_logs_payload(request_body) @@ -631,7 +631,7 @@ def test_sanitize_request_body_for_spend_logs_payload_mixed_types(): assert sanitized["number"] == 42 assert sanitized["nested"]["list"][0] == "short" assert len(sanitized["nested"]["list"][1]) == expected_length - assert len(sanitized["nested"]["dict"]["key"]) == expected_length + assert len(sanitized["nested"]["dict"]["value"]) == expected_length def test_sanitize_request_body_for_spend_logs_payload_uses_runtime_env_override( @@ -2781,10 +2781,12 @@ def test_proxy_server_request_payload_redacts_provider_credentials(mock_should_s assert parsed["messages"] == [{"role": "user", "content": "hello"}] -def test_sanitize_request_body_keeps_credential_named_fields_by_default() -> None: - response: Final = {"system_fingerprint": "fp_123", "usage": {"prompt_tokens": 1}} +def test_sanitize_response_redacts_credential_named_fields() -> None: + response: Final = {"access_token": "canary-oauth-token", "usage": {"prompt_tokens": 1}} - assert _sanitize_request_body_for_spend_logs_payload({"response": response}) == {"response": response} + assert _sanitize_request_body_for_spend_logs_payload({"response": response}) == { + "response": {"access_token": REDACTED_BY_LITELM_STRING, "usage": {"prompt_tokens": 1}} + } @patch("litellm.proxy.spend_tracking.spend_tracking_utils.should_store_prompts_and_responses_in_spend_logs")