diff --git a/openhands-sdk/openhands/sdk/utils/redact.py b/openhands-sdk/openhands/sdk/utils/redact.py index a8488c433b..d204426e46 100644 --- a/openhands-sdk/openhands/sdk/utils/redact.py +++ b/openhands-sdk/openhands/sdk/utils/redact.py @@ -302,16 +302,18 @@ def redact_text_secrets(text: str) -> str: text = re.sub(r"api_key='[^']*'", "api_key=''", text) text = re.sub(r'api_key="[^"]*"', 'api_key=""', text) - # Dict entries with sensitive key names + # Dict entries with sensitive key names (case-insensitive, like is_secret_key) text = re.sub( r"('[A-Z_]*(?:KEY|SECRET|TOKEN|PASSWORD)[A-Z_]*':\s*')[^']*(')", r"\g<1>\2", text, + flags=re.IGNORECASE, ) text = re.sub( r'("[A-Z_]*(?:KEY|SECRET|TOKEN|PASSWORD)[A-Z_]*":\s*")[^"]*(")', r"\g<1>\2", text, + flags=re.IGNORECASE, ) # URL query params diff --git a/tests/sdk/utils/test_redact.py b/tests/sdk/utils/test_redact.py index f211bd4a36..ff56e6432d 100644 --- a/tests/sdk/utils/test_redact.py +++ b/tests/sdk/utils/test_redact.py @@ -2,6 +2,7 @@ from openhands.sdk.utils.redact import ( SENSITIVE_URL_PARAMS, + redact_text_secrets, redact_url_credentials, redact_url_credentials_in_text, redact_url_params, @@ -251,3 +252,35 @@ def test_matches_whole_url_like_anchored_helper(self): """For a bare whole-URL string both helpers agree.""" url = "https://oauth2:SECRET@gitlab.com/org/repo.git" assert redact_url_credentials_in_text(url) == redact_url_credentials(url) + + +# --------------------------------------------------------------------------- +# redact_text_secrets +# --------------------------------------------------------------------------- + + +class TestRedactTextSecretsDictKeys: + """Dict-entry redaction is case-insensitive, like is_secret_key. + + Regression tests for https://github.com/OpenHands/software-agent-sdk/issues/4505 + """ + + def test_redacts_lowercase_and_mixed_case_dict_keys(self): + text = ( + "{'api_key': 's3cr3t', \"token\": \"tok-XYZ\", " + "'UserPassword': 'p@ssw0rd', 'normal': 'keep-me'}" + ) + assert redact_text_secrets(text) == ( + "{'api_key': '', \"token\": \"\", " + "'UserPassword': '', 'normal': 'keep-me'}" + ) + + def test_uppercase_dict_keys_still_redacted(self): + text = "{'API_KEY': 'abc', \"MY_SECRET\": \"def\"}" + assert redact_text_secrets(text) == ( + "{'API_KEY': '', \"MY_SECRET\": \"\"}" + ) + + def test_non_sensitive_entries_unchanged(self): + text = "{'name': 'alice', 'path': '/tmp/x'}" + assert redact_text_secrets(text) == text