diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 8a1b2ebca55..f99ba83daba 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -1625,7 +1625,7 @@ _REQUEST_METADATA_KEYS: Final = ("metadata", "litellm_metadata") _OBJECT_MAPPING: Final = TypeAdapter(Mapping[object, object]) _OBJECT_LIST: Final = TypeAdapter(list[object]) _IPV4_ADDRESS: Final = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b") -_IPV6_ADDRESS_CANDIDATE: Final = re.compile(r"(? str: diff --git a/tests/code_coverage_tests/recursive_detector.py b/tests/code_coverage_tests/recursive_detector.py index 7166891bb1a..e84915d95f2 100644 --- a/tests/code_coverage_tests/recursive_detector.py +++ b/tests/code_coverage_tests/recursive_detector.py @@ -80,6 +80,7 @@ IGNORE_FUNCTIONS = [ "_replace_string_leaves", # bounded by the nesting depth of a safe_json_structure output (a finite JSON tree, no cycles possible). "_sort_processed_sets", # bounded by the nesting depth of the log-record extra it walks (a finite JSON tree, no cycles possible). "scrub_json_strings", # max depth set (MAX_SCRUB_DEPTH); fails closed by returning "[Filtered]" for anything nested past the cap. + "_without_requester_ip_address", # walks only the fresh tree _sanitize_request_body_for_spend_logs_payload returns (mapping cycles already cut), so it never goes deeper than that sanitizer did. ] diff --git a/tests/unit/proxy/spend_tracking/test_spend_tracking_utils.py b/tests/unit/proxy/spend_tracking/test_spend_tracking_utils.py index a9784ab5d9d..ef45ba667b5 100644 --- a/tests/unit/proxy/spend_tracking/test_spend_tracking_utils.py +++ b/tests/unit/proxy/spend_tracking/test_spend_tracking_utils.py @@ -6701,3 +6701,28 @@ def test_failure_error_information_masks_ip_addresses_before_long_text_is_trunca assert LITELLM_TRUNCATED_PAYLOAD_FIELD in stored_text assert any(char.isdigit() for char in kept_head_and_tail) is not disabled assert sanitized["error_code"] == "500" + + +@pytest.mark.parametrize( + "address", ["203.0.113.9", "2001:db8::9", "1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7::", "::1:2:3:4:5:6:7", "fe80::1"] +) +def test_failure_error_information_masks_every_ip_address_form_when_ip_logging_is_disabled( + monkeypatch: pytest.MonkeyPatch, address: str +) -> None: + from litellm.proxy.proxy_server import general_settings + + monkeypatch.setitem(general_settings, "disable_requester_ip_address_logging", True) + + sanitized: Final = sanitize_error_information_for_spend_logs( + { + "error_code": "403", + "error_message": f"IP address {address} not allowed.", + "traceback": f"peer [{address}]:443", + } + ) + + assert sanitized == { + "error_code": "403", + "error_message": "IP address REDACTED_BY_LITELM not allowed.", + "traceback": "peer [REDACTED_BY_LITELM]:443", + }