Merge pull request #21723 from BerriAI/litellm_fix_cost_token_masking

[Fix] Model Info: input_cost_per_token masked in UI
This commit is contained in:
yuneng-jiang
2026-02-20 20:36:22 -08:00
committed by GitHub
2 changed files with 42 additions and 0 deletions
@@ -8,6 +8,7 @@ class SensitiveDataMasker:
def __init__(
self,
sensitive_patterns: Optional[Set[str]] = None,
non_sensitive_overrides: Optional[Set[str]] = None,
visible_prefix: int = 4,
visible_suffix: int = 4,
mask_char: str = "*",
@@ -26,6 +27,10 @@ class SensitiveDataMasker:
"fingerprint",
"tenancy",
}
# If any key segment matches one of these, the key is not considered sensitive
# even if it also matches a sensitive pattern. For example, "input_cost_per_token"
# contains "token" but "cost" overrides that — it's a pricing field, not a secret.
self.non_sensitive_overrides = non_sensitive_overrides or {"cost"}
self.visible_prefix = visible_prefix
self.visible_suffix = visible_suffix
@@ -56,6 +61,13 @@ class SensitiveDataMasker:
# This avoids false positives like "max_tokens" matching "token"
# but still catches "api_key", "access_token", etc.
key_segments = key_lower.replace("-", "_").split("_")
# If any segment matches a non-sensitive override, the key is not sensitive.
# For example, "input_cost_per_token" contains "token" but also "cost",
# so it should not be masked — it's a pricing field, not a secret.
if any(override in key_segments for override in self.non_sensitive_overrides):
return False
result = any(pattern in key_segments for pattern in self.sensitive_patterns)
return result
@@ -121,3 +121,33 @@ def test_lists_with_sensitive_keys_are_masked():
# non-sensitive list should remain unchanged
assert masked["tags"] == ["prod", "test"]
def test_cost_per_token_fields_not_masked():
"""
Regression test: cost fields like input_cost_per_token contain "token" in their name
but should NOT be masked — they are pricing fields, not secrets.
Previously, these would be displayed as e.g. "3.60*******e-06" in the UI.
"""
masker = SensitiveDataMasker()
data = {
"input_cost_per_token": 3.6e-06,
"output_cost_per_token": 1.2e-05,
"cache_read_input_token_cost": 9.0e-07,
"cache_creation_input_token_cost": 3.75e-06,
# Real secret fields should still be masked
"api_key": "sk-1234567890abcdef",
"access_token": "my-secret-token",
}
masked = masker.mask_dict(data)
# Cost fields must not be masked
assert masked["input_cost_per_token"] == 3.6e-06
assert masked["output_cost_per_token"] == 1.2e-05
assert masked["cache_read_input_token_cost"] == 9.0e-07
assert masked["cache_creation_input_token_cost"] == 3.75e-06
# Actual secrets must still be masked
assert "*" in masked["api_key"]
assert "*" in masked["access_token"]