mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
fix(hide-secrets): scan the first token of an assignment and ignore surrounding punctuation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
ecde7295d9
commit
a74f12d846
3 changed files with 52 additions and 7 deletions
|
|
@ -513,8 +513,9 @@ def _quoted_assignments(text: str) -> tuple[str, ...]:
|
|||
f'{key} = "{value}"'
|
||||
for section in parser
|
||||
for key, values in parser.items(section)
|
||||
for value in values.splitlines()
|
||||
if value and '"' not in value
|
||||
for line in values.splitlines()
|
||||
for value in line.split()[:1]
|
||||
if '"' not in value
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import re
|
||||
from collections.abc import Generator, Mapping
|
||||
from string import punctuation
|
||||
from typing import Final
|
||||
|
||||
from detect_secrets.plugins.keyword import (
|
||||
|
|
@ -15,6 +16,13 @@ _ISO_8601_TIMESTAMP: Final = re.compile(
|
|||
r"\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?"
|
||||
)
|
||||
_URL_WITHOUT_USERINFO_OR_QUERY: Final = re.compile(r"[A-Za-z][A-Za-z0-9+.-]*://[^\s@?]*")
|
||||
_BENIGN_VALUES: Final = (
|
||||
_ENVIRONMENT_REFERENCE,
|
||||
_ENVIRONMENT_VARIABLE_NAME,
|
||||
_LOWERCASE_WORD_SEQUENCE,
|
||||
_ISO_8601_TIMESTAMP,
|
||||
_URL_WITHOUT_USERINFO_OR_QUERY,
|
||||
)
|
||||
|
||||
|
||||
class CredentialKeywordDetector(KeywordDetector): # pyright: ignore[reportUntypedBaseClass] # detect_secrets ships no type information
|
||||
|
|
@ -30,14 +38,11 @@ class CredentialKeywordDetector(KeywordDetector): # pyright: ignore[reportUntyp
|
|||
self.minimum_length = minimum_length
|
||||
|
||||
def _is_credential(self, value: str) -> bool:
|
||||
core: Final = value.strip(punctuation)
|
||||
return (
|
||||
len(value) >= self.minimum_length
|
||||
and _CREDENTIAL_VALUE.fullmatch(value) is not None
|
||||
and _ENVIRONMENT_REFERENCE.fullmatch(value) is None
|
||||
and _ENVIRONMENT_VARIABLE_NAME.fullmatch(value) is None
|
||||
and _LOWERCASE_WORD_SEQUENCE.fullmatch(value) is None
|
||||
and _ISO_8601_TIMESTAMP.fullmatch(value) is None
|
||||
and _URL_WITHOUT_USERINFO_OR_QUERY.fullmatch(value) is None
|
||||
and all(benign.fullmatch(core) is None for benign in _BENIGN_VALUES)
|
||||
)
|
||||
|
||||
def analyze_string(
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ def test_scan_message_preserves_quoted_benign_identifiers():
|
|||
'{"password": "YOUR_API_KEY_HERE", "client_secret": "correcthorsebattery"}',
|
||||
"correcthorsebattery",
|
||||
),
|
||||
("docker run -e REDIS_PASSWORD=aB3dE6gH9jK2mN5p \\\n -e REDIS_PORT=6379 redis", "aB3dE6gH9jK2mN5p"),
|
||||
("DB_PASSWORD=Zx4Kp9Lm2Qr7Ns3Vt && echo done", "Zx4Kp9Lm2Qr7Ns3Vt"),
|
||||
("password = Zx4Kp9Lm2Qr7Ns3Vt # rotate me", "Zx4Kp9Lm2Qr7Ns3Vt"),
|
||||
("my db password: Zx4Kp9Lm2Qr7Ns3Vt.", "Zx4Kp9Lm2Qr7Ns3Vt"),
|
||||
],
|
||||
ids=[
|
||||
"env-password",
|
||||
|
|
@ -139,6 +143,10 @@ def test_scan_message_preserves_quoted_benign_identifiers():
|
|||
"password-only-url-under-a-credential-key",
|
||||
"timestamp-prefixed-password",
|
||||
"credential-after-a-rejected-placeholder",
|
||||
"docker-flag-with-a-line-continuation",
|
||||
"shell-command-after-the-value",
|
||||
"inline-comment-after-the-value",
|
||||
"sentence-ending-in-the-value",
|
||||
],
|
||||
)
|
||||
def test_scan_message_redacts_credentials_assigned_to_credential_keys(content, secret):
|
||||
|
|
@ -147,6 +155,23 @@ def test_scan_message_redacts_credentials_assigned_to_credential_keys(content, s
|
|||
assert secret not in guardrail.redact_text(content)
|
||||
|
||||
|
||||
def test_scan_message_redacts_only_the_first_token_of_a_shell_assignment():
|
||||
guardrail = _guardrail()
|
||||
content = "docker run -e REDIS_PASSWORD=aB3dE6gH9jK2mN5p \\\n -e REDIS_PORT=6379 redis && echo done"
|
||||
|
||||
assert (
|
||||
guardrail.redact_text(content)
|
||||
== "docker run -e REDIS_PASSWORD=[REDACTED] \\\n -e REDIS_PORT=6379 redis && echo done"
|
||||
)
|
||||
|
||||
|
||||
def test_scan_message_closes_a_yaml_block_at_the_next_unindented_line():
|
||||
guardrail = _guardrail()
|
||||
content = "api_key: >\n aB3dE6gH9jK2mN5p\nSteps\n Rotate-Before-Friday please"
|
||||
|
||||
assert guardrail.redact_text(content) == "api_key: >\n [REDACTED]\nSteps\n Rotate-Before-Friday please"
|
||||
|
||||
|
||||
def test_scan_message_redacts_every_credential_on_one_line():
|
||||
"""detect_secrets keeps the first hit per pattern, so a JSON object holding two credentials
|
||||
would leave the second one in the prompt."""
|
||||
|
|
@ -204,6 +229,13 @@ def test_scan_message_redacts_every_credential_on_one_line():
|
|||
'password_reset_url: "https://example.com/reset-password/flow"',
|
||||
'secret_docs_url: "https://example.com/reset-password/flow#step-2"',
|
||||
'{"api_key_created_at": "2026-09-08T17:38:40Z", "password_reset_url": "https://example.com/reset/flow"}',
|
||||
"secret_sauce: tomatoes-basil-garlic-oregano.",
|
||||
"secret_docs_url: https://example.com/docs/keys, then rotate",
|
||||
"api_key_created_at: 2026-09-08T17:38:40Z; api_key_env: OPENAI_API_KEY!",
|
||||
"api_key: $OPENAI_API_KEY",
|
||||
'api_key: "${OPENAI_API_KEY}"',
|
||||
"private_key_path: /keys/prod/server-cert.pem",
|
||||
"password_hint: your usual one followed by Ticket-LIT7049-Suffix",
|
||||
],
|
||||
ids=[
|
||||
"prose-password",
|
||||
|
|
@ -251,6 +283,13 @@ def test_scan_message_redacts_every_credential_on_one_line():
|
|||
"url-under-a-credential-key",
|
||||
"fragment-url-under-a-credential-key",
|
||||
"metadata-object-under-credential-keys",
|
||||
"hyphenated-english-ending-a-sentence",
|
||||
"url-followed-by-a-clause",
|
||||
"timestamp-and-env-name-with-trailing-punctuation",
|
||||
"shell-variable-reference",
|
||||
"quoted-braced-shell-variable-reference",
|
||||
"absolute-path-under-a-credential-key",
|
||||
"sentence-holding-a-later-mixed-case-token",
|
||||
],
|
||||
)
|
||||
def test_scan_message_keeps_benign_values(content):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue