From 7b580ce0d99eded397fbe53966c996d8b34327ed Mon Sep 17 00:00:00 2001 From: Yucheng He Date: Tue, 8 Sep 2026 10:38:07 -0700 Subject: [PATCH] fix(hide-secrets): accept punctuation in a credential value The value filter only allowed the URL-safe Base64 alphabet, so a password such as hunter2!brahms or p@ssw0rd!2026 passed through unredacted while the upstream keyword plugin had already matched it. The filter now rejects only whitespace and brackets, which keeps function calls, subscripts and sentences out while letting symbol-heavy passwords through. --- .../secrets_plugins/credential_keyword.py | 2 +- .../test_secret_detection.py | 25 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/enterprise/litellm_enterprise/enterprise_callbacks/secrets_plugins/credential_keyword.py b/enterprise/litellm_enterprise/enterprise_callbacks/secrets_plugins/credential_keyword.py index d9afc751daf..c1f578817ce 100644 --- a/enterprise/litellm_enterprise/enterprise_callbacks/secrets_plugins/credential_keyword.py +++ b/enterprise/litellm_enterprise/enterprise_callbacks/secrets_plugins/credential_keyword.py @@ -4,7 +4,7 @@ from typing import Final from detect_secrets.plugins.keyword import KeywordDetector -_CREDENTIAL_VALUE: Final = re.compile(r"[A-Za-z0-9_.~+/-]+={0,2}") +_CREDENTIAL_VALUE: Final = re.compile(r"[^\s()\[\]]+") _ENVIRONMENT_REFERENCE: Final = re.compile(r"os\.environ/\w+", re.IGNORECASE) _ENVIRONMENT_VARIABLE_NAME: Final = re.compile(r"[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+") _LOWERCASE_WORD_SEQUENCE: Final = re.compile(r"[a-z]+(?:[-._/][a-z]+)+") diff --git a/tests/test_litellm/enterprise/enterprise_callbacks/test_secret_detection.py b/tests/test_litellm/enterprise/enterprise_callbacks/test_secret_detection.py index 4def77173ed..fdc3acc203c 100644 --- a/tests/test_litellm/enterprise/enterprise_callbacks/test_secret_detection.py +++ b/tests/test_litellm/enterprise/enterprise_callbacks/test_secret_detection.py @@ -90,6 +90,12 @@ def test_scan_message_preserves_quoted_benign_identifiers(): "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", ), ("password=aB3dE6gH9jK2", "aB3dE6gH9jK2"), + ("api_key: hunter2!brahms", "hunter2!brahms"), + ('db_password: "p@ssw0rd!2026"', "p@ssw0rd!2026"), + ( + 'url: "postgresql://user:s3cr3t@db-host:5432/app"', + "postgresql://user:s3cr3t@db-host:5432/app", + ), ], ids=[ "env-password", @@ -108,6 +114,9 @@ def test_scan_message_preserves_quoted_benign_identifiers(): "django-secret-key", "slashed-aws-secret", "shortest-accepted-value", + "punctuation-bearing-password", + "symbol-heavy-password", + "connection-string-under-a-credential-key", ], ) def test_scan_message_redacts_credentials_assigned_to_credential_keys(content, secret): @@ -139,11 +148,11 @@ def test_scan_message_redacts_credentials_assigned_to_credential_keys(content, s "langfuse_secret: os.environ/LANGFUSE_PROJECT1_SECRET", "api_key = OPENAI_API_KEY", "password = pwd12345678", - "api_key: hunter2!brahms", "model_key: gpt-4o-mini-2024-07-18", "openrouter/anthropic/claude-3-5-sonnet-20240620", '{"content-type": "application/json"}', "passwordless_login: enabled-for-all-users", + 'password: "I forgot mine, can you reset it"', "secret_sauce: tomatoes-basil-garlic-oregano", "user_secret_question: what-was-your-first-pet", "password_reset_url: example.com/reset-password/flow", @@ -153,6 +162,12 @@ def test_scan_message_redacts_credentials_assigned_to_credential_keys(content, s 'api_key = "OPENAI_API_KEY"', "model_list:\n - litellm_params:\n api_key: 'PERPLEXITY_API_KEY'", 'config = build(_provider("ve_missing", api_key_env="VE_MISSING_KEY"))', + "api_key = get_api_key_from_env()", + "api_key = get_secret_str(MISTRAL_OCR_API_KEY_ENV_VAR)", + "secret_manager = MagicMock(spec=BaseSecretManager)", + "api_key = self.resolve_server_api_key(", + "api_key = sys.argv[1]", + "password = credentials[environment]", ], ids=[ "prose-password", @@ -175,11 +190,11 @@ def test_scan_message_redacts_credentials_assigned_to_credential_keys(content, s "env-reference-nested", "env-variable-name", "below-minimum-length", - "non-credential-charset", "model-name", "namespaced-model-name", "media-type", "hyphenated-english", + "quoted-sentence-under-a-credential-key", "hyphenated-phrase", "hyphenated-question", "url-under-credential-key", @@ -189,6 +204,12 @@ def test_scan_message_redacts_credentials_assigned_to_credential_keys(content, s "quoted-env-variable-name", "quoted-env-name-in-a-config", "quoted-env-name-in-a-code-paste", + "bare-call", + "call-with-an-argument", + "keyword-argument-call", + "unclosed-call", + "positional-subscript", + "keyed-subscript", ], ) def test_scan_message_keeps_benign_values(content):