diff --git a/enterprise/litellm_enterprise/enterprise_callbacks/secret_detection.py b/enterprise/litellm_enterprise/enterprise_callbacks/secret_detection.py index 431a8212507..c61b6a59301 100644 --- a/enterprise/litellm_enterprise/enterprise_callbacks/secret_detection.py +++ b/enterprise/litellm_enterprise/enterprise_callbacks/secret_detection.py @@ -495,7 +495,17 @@ def _parseable_lines(text: str) -> Generator[str, None, None]: if not stripped: yield line elif stripped[0] in "#;": - open_option = False + # configparser reads a comment inside a value without closing it, so leaving + # open_option alone keeps the indented lines under the comment reachable. + yield line + elif assignment is not None: + open_option = True + # Dedenting reaches the assignments inside a pasted config, and the line number + # keeps every key distinct so a config repeating api_key per model keeps them all. + yield f"{assignment.group()[:-1].strip()}_{number}{stripped[assignment.end() - 1:]}" + elif line[0].isspace() and open_option: + # An indented line inside an open value is part of that value to configparser, + # brackets included, so this has to be tested before the section header below. yield line elif stripped[0] == "[": open_option = False @@ -503,13 +513,6 @@ def _parseable_lines(text: str) -> Generator[str, None, None]: # it aborts the whole parse, taking every assignment below down with it. if "]" in stripped[2:]: yield line - elif assignment is not None: - open_option = True - # Dedenting reaches the assignments inside a pasted config, and the line number - # keeps every key distinct so a config repeating api_key per model keeps them all. - yield f"{assignment.group()[:-1].strip()}_{number}{stripped[assignment.end() - 1:]}" - elif line[0].isspace() and open_option: - yield line else: open_option = False 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 85dc1ead6c8..4def77173ed 100644 --- a/tests/test_litellm/enterprise/enterprise_callbacks/test_secret_detection.py +++ b/tests/test_litellm/enterprise/enterprise_callbacks/test_secret_detection.py @@ -332,6 +332,10 @@ def test_scan_message_keeps_every_value_when_a_config_repeats_a_key(): ("api_key: >\n aB3dE6gH9jK2mN5p", "aB3dE6gH9jK2mN5p"), ("api_key: |-\n aB3dE6gH9jK2mN5p", "aB3dE6gH9jK2mN5p"), ("secret= \\\n aB3dE6gH9jK2mN5p", "aB3dE6gH9jK2mN5p"), + ("password =\n# rotate me\n Zx4Kp9Lm2Qr7Ns3Vt", "Zx4Kp9Lm2Qr7Ns3Vt"), + ("api_key =\n; rotate me\n aB3dE6gH9jK2mN5p", "aB3dE6gH9jK2mN5p"), + (" # pasted from the vault\napi_key=aB3dE6gH9jK2mN5p", "aB3dE6gH9jK2mN5p"), + (" [db]\napi_key=aB3dE6gH9jK2mN5p", "aB3dE6gH9jK2mN5p"), ], ids=[ "flat-assignment", @@ -344,6 +348,10 @@ def test_scan_message_keeps_every_value_when_a_config_repeats_a_key(): "yaml-folded-block", "yaml-literal-block", "backslash-continuation", + "comment-inside-a-value", + "semicolon-comment-inside-a-value", + "indented-comment-above", + "indented-section-header-above", ], ) def test_scan_message_still_sees_assignments_sharing_a_message_with_a_vendor_key(