From c01034f9cbef6e2f4283a6f451daa1e62ec3a834 Mon Sep 17 00:00:00 2001 From: cyq <61975706+cyq1017@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:53:33 +0800 Subject: [PATCH] fix(settings): scrub camelCase secret keys (#3707) --- src/settings_scrub.py | 12 +++++++++++- tests/test_settings_scrub.py | 20 +++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/settings_scrub.py b/src/settings_scrub.py index 7dc462f2e..926ff611c 100644 --- a/src/settings_scrub.py +++ b/src/settings_scrub.py @@ -12,6 +12,8 @@ tunnel / reverse proxy. Scrubbing is deep (recurses nested dicts/lists) and keye on secret-shaped names. """ +import re + _SECRET_KEY_PATTERNS = ( "_api_key", "_apikey", "_password", "_passwd", "_pass", "_pwd", "_secret", "_client_secret", "_token", "_access_token", "_refresh_token", @@ -26,8 +28,16 @@ _SENSITIVE_KEY_EXACT = ( ) +def _canonical_key_name(name: str) -> str: + """Normalize common JS-style key names so secret matching is style-agnostic.""" + n = (name or "").replace("-", "_") + n = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", n) + n = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", n) + return n.lower() + + def is_secret_key(name: str) -> bool: - n = (name or "").lower() + n = _canonical_key_name(name) if n in _SECRET_KEY_ALLOW: return False if n in _SENSITIVE_KEY_EXACT: diff --git a/tests/test_settings_scrub.py b/tests/test_settings_scrub.py index 3f772a88c..c8786fe7d 100644 --- a/tests/test_settings_scrub.py +++ b/tests/test_settings_scrub.py @@ -40,7 +40,8 @@ def test_secret_in_list_of_dicts_blanked(): def test_non_secret_keys_preserved(): s = {"keybinds": {"send": "Enter"}, "theme": "dark", "image_model": "x", - "default_endpoint_id": "ep1", "search_result_count": 5, "tts_enabled": True} + "default_endpoint_id": "ep1", "search_result_count": 5, "tts_enabled": True, + "tokenId": "public-id", "keyId": "public-key-id"} assert scrub_settings(s) == s # untouched @@ -71,6 +72,23 @@ def test_exact_name_matches(): assert all(v == "" for v in out.values()), out +def test_camel_case_secret_keys_blanked(): + out = scrub_settings({ + "apiKey": "api-secret", + "accessToken": "access-secret", + "refreshToken": "refresh-secret", + "clientSecret": "client-secret", + "hfToken": "hf-secret", + "nested": {"privateKey": "private-secret"}, + }) + assert out["apiKey"] == "" + assert out["accessToken"] == "" + assert out["refreshToken"] == "" + assert out["clientSecret"] == "" + assert out["hfToken"] == "" + assert out["nested"]["privateKey"] == "" + + def test_non_object_settings_return_empty_mapping(): assert scrub_settings(["not", "settings"]) == {} assert scrub_settings("not settings") == {}