mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-17 04:17:43 +00:00
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:
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user