fix(proxy): mask IPv6 addresses that end or start with :: across eight groups

The masking pattern allowed seven colons, so valid forms like 1:2:3:4:5:6:7::
and ::1:2:3:4:5:6:7 stayed in stored error text with
disable_requester_ip_address_logging on. Also lists the request metadata walker
in the recursion check, since it only walks the tree the spend log sanitizer
already returned.
This commit is contained in:
yucheng-berri 2026-10-10 09:59:13 -07:00
parent ed556d12ca
commit f15c94b1d7
3 changed files with 27 additions and 1 deletions

View file

@ -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"(?<![\w:])(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}(?![\w:])")
_IPV6_ADDRESS_CANDIDATE: Final = re.compile(r"(?<![\w:])(?:[0-9A-Fa-f]{0,4}:){2,8}[0-9A-Fa-f]{0,4}(?![\w:])")
def _masked_ipv6_address(candidate: re.Match[str]) -> str:

View file

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

View file

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