mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(hide-secrets): scan the first token of shell-style assignments regardless of what follows
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
3573154827
commit
04f21ba8ae
2 changed files with 26 additions and 9 deletions
|
|
@ -451,9 +451,9 @@ _CONFIG_SECTION: Final = "litellm-prompt"
|
|||
|
||||
_ASSIGNMENT_LINE: Final = re.compile(r"[^\s\[#;:=][^:=]*[:=]")
|
||||
|
||||
_SHELL_OPERATORS: Final = ";&|"
|
||||
_SHELL_ASSIGNMENT: Final = re.compile(r"(?P<key>[^\s\[#;:=](?:[^:=]*[^\s:=])?)=(?P<value>\S+)")
|
||||
|
||||
_SHELL_TRAILER: Final = re.compile(rf"\\|[{_SHELL_OPERATORS}]+|#.*|\S+=\S*")
|
||||
_SHELL_OPERATORS: Final = ";&|"
|
||||
|
||||
_SCAN_SUFFIX: Final = ".py"
|
||||
|
||||
|
|
@ -491,6 +491,9 @@ def _classify_line(state: tuple[bool, str | None], numbered: tuple[int, str]) ->
|
|||
stripped: Final = line.strip()
|
||||
if not stripped or stripped[0] in "#;":
|
||||
return open_option, None
|
||||
shell_assignment: Final = _SHELL_ASSIGNMENT.match(stripped)
|
||||
if shell_assignment is not None:
|
||||
return True, f"{shell_assignment['key']}_{number}={shell_assignment['value']}"
|
||||
assignment: Final = _ASSIGNMENT_LINE.match(stripped)
|
||||
if assignment is not None:
|
||||
return True, f"{assignment.group()[:-1].strip()}_{number}{stripped[assignment.end() - 1 :]}"
|
||||
|
|
@ -505,14 +508,10 @@ def _parseable_lines(text: str) -> Iterator[str]:
|
|||
|
||||
|
||||
def _lone_value(line: str) -> str | None:
|
||||
tokens: Final = line.split(maxsplit=2)
|
||||
if not tokens or '"' in tokens[0]:
|
||||
tokens: Final = line.split()
|
||||
if not tokens or '"' in tokens[0] or (len(tokens) > 1 and not tokens[1].startswith("#")):
|
||||
return None
|
||||
value: Final = tokens[0].rstrip(_SHELL_OPERATORS)
|
||||
trailer: Final = tokens[1] if len(tokens) > 1 else ""
|
||||
if value != tokens[0] or not trailer or _SHELL_TRAILER.fullmatch(trailer) is not None:
|
||||
return value
|
||||
return None
|
||||
return tokens[0].rstrip(_SHELL_OPERATORS)
|
||||
|
||||
|
||||
def _quoted_assignments(text: str) -> tuple[str, ...]:
|
||||
|
|
|
|||
|
|
@ -120,6 +120,9 @@ def test_scan_message_preserves_quoted_benign_identifiers():
|
|||
("export DB_PASSWORD=Zx4Kp9Lm2Qr7Ns3Vt DB_HOST=db.internal", "Zx4Kp9Lm2Qr7Ns3Vt"),
|
||||
("DB_PASSWORD=Zx4Kp9Lm2Qr7Ns3Vt; systemctl restart app", "Zx4Kp9Lm2Qr7Ns3Vt"),
|
||||
("DB_PASSWORD=Zx4Kp9Lm2Qr7Ns3Vt | tee creds.txt", "Zx4Kp9Lm2Qr7Ns3Vt"),
|
||||
("DB_PASSWORD=Zx4Kp9Lm2Qr7Ns3Vt > setup.log", "Zx4Kp9Lm2Qr7Ns3Vt"),
|
||||
("docker run -e DB_PASSWORD=Zx4Kp9Lm2Qr7Ns3Vt --name app postgres", "Zx4Kp9Lm2Qr7Ns3Vt"),
|
||||
("password=correcthorsebattery please", "correcthorsebattery"),
|
||||
],
|
||||
ids=[
|
||||
"env-password",
|
||||
|
|
@ -153,6 +156,9 @@ def test_scan_message_preserves_quoted_benign_identifiers():
|
|||
"second-assignment-after-the-value",
|
||||
"semicolon-after-the-value",
|
||||
"pipe-after-the-value",
|
||||
"redirect-after-the-value",
|
||||
"docker-flag-after-the-value",
|
||||
"prose-after-a-shell-assignment",
|
||||
],
|
||||
)
|
||||
def test_scan_message_redacts_credentials_assigned_to_credential_keys(content, secret):
|
||||
|
|
@ -171,6 +177,16 @@ def test_scan_message_redacts_only_the_first_token_of_a_shell_assignment():
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("operator", [";", "&&", "|"])
|
||||
def test_scan_message_keeps_a_shell_operator_glued_to_the_value(operator):
|
||||
guardrail = _guardrail()
|
||||
|
||||
assert (
|
||||
guardrail.redact_text(f"DB_PASSWORD=Zx4Kp9Lm2Qr7Ns3Vt{operator} systemctl restart app")
|
||||
== f"DB_PASSWORD=[REDACTED]{operator} systemctl restart app"
|
||||
)
|
||||
|
||||
|
||||
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"
|
||||
|
|
@ -244,6 +260,7 @@ def test_scan_message_redacts_every_credential_on_one_line():
|
|||
"password_hint: your usual one followed by Ticket-LIT7049-Suffix",
|
||||
"Translate this recipe note into French:\nsecret_sauce: Worcestershire sauce",
|
||||
"api_key = Massachusetts (the state, not a key)",
|
||||
"secret_sauce:Worcestershire sauce",
|
||||
],
|
||||
ids=[
|
||||
"prose-password",
|
||||
|
|
@ -300,6 +317,7 @@ def test_scan_message_redacts_every_credential_on_one_line():
|
|||
"sentence-holding-a-later-mixed-case-token",
|
||||
"capitalized-word-starting-a-phrase",
|
||||
"capitalized-word-before-a-parenthetical",
|
||||
"yaml-scalar-without-a-space-after-the-colon",
|
||||
],
|
||||
)
|
||||
def test_scan_message_keeps_benign_values(content):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue