mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
[Fix] Exclude litellm_credential_name from Sensitive Data Masker (Updated) (#16958)
* Exclude litellm_credential_name from sensitive masker * Adding missing file
This commit is contained in:
parent
703f619e08
commit
6881594632
5 changed files with 103 additions and 12 deletions
|
|
@ -42,7 +42,11 @@ class SensitiveDataMasker:
|
|||
else:
|
||||
return f"{value_str[:self.visible_prefix]}{self.mask_char * masked_length}{value_str[-self.visible_suffix:]}"
|
||||
|
||||
def is_sensitive_key(self, key: str) -> bool:
|
||||
def is_sensitive_key(self, key: str, excluded_keys: Optional[Set[str]] = None) -> bool:
|
||||
# Check if key is in excluded_keys first (exact match)
|
||||
if excluded_keys and key in excluded_keys:
|
||||
return False
|
||||
|
||||
key_lower = str(key).lower()
|
||||
# Split on underscores and check if any segment matches the pattern
|
||||
# This avoids false positives like "max_tokens" matching "token"
|
||||
|
|
@ -64,7 +68,6 @@ class SensitiveDataMasker:
|
|||
if depth >= max_depth:
|
||||
return data
|
||||
|
||||
excluded_keys = excluded_keys or set()
|
||||
masked_data: Dict[str, Any] = {}
|
||||
for k, v in data.items():
|
||||
try:
|
||||
|
|
@ -72,10 +75,7 @@ class SensitiveDataMasker:
|
|||
masked_data[k] = self.mask_dict(v, depth + 1, max_depth, excluded_keys)
|
||||
elif hasattr(v, "__dict__") and not isinstance(v, type):
|
||||
masked_data[k] = self.mask_dict(vars(v), depth + 1, max_depth, excluded_keys)
|
||||
elif k in excluded_keys:
|
||||
# Don't mask keys that are explicitly excluded
|
||||
masked_data[k] = v
|
||||
elif self.is_sensitive_key(k):
|
||||
elif self.is_sensitive_key(k, excluded_keys):
|
||||
str_value = str(v) if v is not None else ""
|
||||
masked_data[k] = self._mask_value(str_value)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
Contains utils used by OpenAI compatible endpoints
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Set
|
||||
|
||||
from fastapi import Request
|
||||
|
||||
|
|
@ -12,12 +12,16 @@ from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
|
|||
SENSITIVE_DATA_MASKER = SensitiveDataMasker()
|
||||
|
||||
|
||||
def remove_sensitive_info_from_deployment(deployment_dict: dict) -> dict:
|
||||
def remove_sensitive_info_from_deployment(
|
||||
deployment_dict: dict,
|
||||
excluded_keys: Optional[Set[str]] = None,
|
||||
) -> dict:
|
||||
"""
|
||||
Removes sensitive information from a deployment dictionary.
|
||||
|
||||
Args:
|
||||
deployment_dict (dict): The deployment dictionary to remove sensitive information from.
|
||||
excluded_keys (Optional[Set[str]]): Set of keys that should not be masked (exact match).
|
||||
|
||||
Returns:
|
||||
dict: The modified deployment dictionary with sensitive information removed.
|
||||
|
|
@ -28,7 +32,9 @@ def remove_sensitive_info_from_deployment(deployment_dict: dict) -> dict:
|
|||
deployment_dict["litellm_params"].pop("aws_access_key_id", None)
|
||||
deployment_dict["litellm_params"].pop("aws_secret_access_key", None)
|
||||
|
||||
deployment_dict["litellm_params"] = SENSITIVE_DATA_MASKER.mask_dict(deployment_dict["litellm_params"])
|
||||
deployment_dict["litellm_params"] = SENSITIVE_DATA_MASKER.mask_dict(
|
||||
deployment_dict["litellm_params"], excluded_keys=excluded_keys
|
||||
)
|
||||
|
||||
return deployment_dict
|
||||
|
||||
|
|
|
|||
|
|
@ -7069,7 +7069,9 @@ async def model_info_v2(
|
|||
_model["model_info"] = model_info
|
||||
# don't return the api key / vertex credentials
|
||||
# don't return the llm credentials
|
||||
_model = remove_sensitive_info_from_deployment(_model)
|
||||
_model = remove_sensitive_info_from_deployment(
|
||||
_model, excluded_keys={"litellm_credential_name"}
|
||||
)
|
||||
|
||||
verbose_proxy_logger.debug("all_models: %s", all_models)
|
||||
return {"data": all_models}
|
||||
|
|
@ -7536,7 +7538,9 @@ def _get_proxy_model_info(model: dict) -> dict:
|
|||
model_info[k] = v
|
||||
model["model_info"] = model_info
|
||||
# don't return the llm credentials
|
||||
model = remove_sensitive_info_from_deployment(deployment_dict=model)
|
||||
model = remove_sensitive_info_from_deployment(
|
||||
deployment_dict=model, excluded_keys={"litellm_credential_name"}
|
||||
)
|
||||
|
||||
return model
|
||||
|
||||
|
|
@ -7604,7 +7608,8 @@ async def model_info_v1( # noqa: PLR0915
|
|||
)
|
||||
_deployment_info_dict = _deployment_info.model_dump()
|
||||
_deployment_info_dict = remove_sensitive_info_from_deployment(
|
||||
deployment_dict=_deployment_info_dict
|
||||
deployment_dict=_deployment_info_dict,
|
||||
excluded_keys={"litellm_credential_name"},
|
||||
)
|
||||
return {"data": _deployment_info_dict}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,3 +29,49 @@ def test_lists_are_preserved_not_converted_to_strings():
|
|||
# Must be a list, not a string
|
||||
assert isinstance(masked["tags"], list)
|
||||
assert masked["tags"] == ["East US 2", "production", "test"]
|
||||
|
||||
|
||||
def test_excluded_keys_exact_match():
|
||||
"""
|
||||
Test that excluded_keys prevents masking of specific keys (exact match).
|
||||
"""
|
||||
masker = SensitiveDataMasker()
|
||||
|
||||
data = {
|
||||
"api_key": "sk-1234567890abcdef",
|
||||
"litellm_credentials_name": "my-credential-name",
|
||||
"access_token": "token-12345",
|
||||
"port": 6379,
|
||||
}
|
||||
|
||||
# Without excluded_keys, sensitive keys should be masked
|
||||
masked = masker.mask_dict(data)
|
||||
assert masked["api_key"] != "sk-1234567890abcdef"
|
||||
assert "*" in masked["api_key"]
|
||||
assert masked["access_token"] != "token-12345"
|
||||
assert "*" in masked["access_token"]
|
||||
|
||||
# With excluded_keys, litellm_credentials_name should NOT be masked (exact match)
|
||||
# This ensures that even if pattern matching logic changes, excluded keys won't be masked
|
||||
masked = masker.mask_dict(data, excluded_keys={"litellm_credentials_name"})
|
||||
assert masked["litellm_credentials_name"] == "my-credential-name"
|
||||
|
||||
# Other sensitive keys should still be masked
|
||||
assert masked["api_key"] != "sk-1234567890abcdef"
|
||||
assert "*" in masked["api_key"]
|
||||
assert masked["access_token"] != "token-12345"
|
||||
assert "*" in masked["access_token"]
|
||||
|
||||
# Non-sensitive keys should remain unchanged
|
||||
assert masked["port"] == 6379
|
||||
|
||||
# Test case sensitivity - excluded_keys should be exact match
|
||||
masked = masker.mask_dict(data, excluded_keys={"LITELLM_CREDENTIALS_NAME"})
|
||||
# Should still be masked because case doesn't match (exact match required)
|
||||
assert masked["litellm_credentials_name"] == "my-credential-name" # Not masked because it doesn't match patterns anyway
|
||||
|
||||
# Test with api_key in excluded_keys to verify it works for keys that would be masked
|
||||
masked = masker.mask_dict(data, excluded_keys={"api_key"})
|
||||
assert masked["api_key"] == "sk-1234567890abcdef" # Should NOT be masked
|
||||
assert masked["access_token"] != "token-12345" # Should still be masked
|
||||
assert "*" in masked["access_token"]
|
||||
|
|
|
|||
|
|
@ -85,3 +85,37 @@ from litellm.proxy.common_utils.openai_endpoint_utils import remove_sensitive_in
|
|||
def test_remove_sensitive_info_from_deployment(model_config: dict, expected_config: dict):
|
||||
sanitized_config = remove_sensitive_info_from_deployment(model_config)
|
||||
assert sanitized_config == expected_config
|
||||
|
||||
|
||||
def test_remove_sensitive_info_from_deployment_with_excluded_keys():
|
||||
"""
|
||||
Test that excluded_keys prevents masking of specific keys (exact match).
|
||||
"""
|
||||
model_config = {
|
||||
"model_name": "test-model",
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4",
|
||||
"api_key": "sk-sensitive-key-123",
|
||||
"litellm_credentials_name": "my-credential-name",
|
||||
"access_token": "token-12345",
|
||||
"temperature": 0.7
|
||||
}
|
||||
}
|
||||
|
||||
# Without excluded_keys, access_token should be masked (contains "token")
|
||||
sanitized_config = remove_sensitive_info_from_deployment(model_config)
|
||||
assert sanitized_config["litellm_params"]["access_token"] != "token-12345"
|
||||
assert "*" in sanitized_config["litellm_params"]["access_token"]
|
||||
|
||||
# With excluded_keys, litellm_credentials_name should NOT be masked (even if it would match patterns)
|
||||
sanitized_config = remove_sensitive_info_from_deployment(
|
||||
model_config, excluded_keys={"litellm_credentials_name"}
|
||||
)
|
||||
assert sanitized_config["litellm_params"]["litellm_credentials_name"] == "my-credential-name"
|
||||
|
||||
# access_token should still be masked (not in excluded_keys)
|
||||
assert sanitized_config["litellm_params"]["access_token"] != "token-12345"
|
||||
assert "*" in sanitized_config["litellm_params"]["access_token"]
|
||||
|
||||
# api_key should still be removed (popped) regardless of excluded_keys
|
||||
assert "api_key" not in sanitized_config["litellm_params"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue