From a57306654337cd6d4c0fc559b17067adbd247182 Mon Sep 17 00:00:00 2001 From: srojk34 <286497132+srojk34@users.noreply.github.com> Date: Sun, 5 Jul 2026 17:43:47 +0300 Subject: [PATCH] fix(redact): skip env-lookup exception for JSON/YAML config field redaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _redact_env already skips redaction when a KEY=value assignment's value is a programmatic env lookup (os.getenv(...), os.environ[...], process.env.X) per issue #2852 — masking it would corrupt a code snippet, not redact a secret. _redact_json (JSON "key": "value" syntax) and _redact_yaml (unquoted key: value syntax) are separate closures in the same function and never got the same check, so the identical code-snippet-in-config- syntax case still gets mangled: {"apiKey": "os.getenv('OPENAI_API_KEY')"} -> {"apiKey": "os.get...EY')"} api_key: os.getenv("OPENAI_API_KEY") -> api_key: os.get...EY") Fix: apply the same _ENV_LOOKUP_VALUE_RE.match(value) check in both closures before masking, mirroring _redact_env exactly. Real secret values in JSON/YAML syntax are still redacted (verified live and via new tests) — this only skips the case where the "value" already look like a code snippet. --- agent/redact.py | 10 ++++++++++ tests/agent/test_redact.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/agent/redact.py b/agent/redact.py index 21e490a304e..6b37d2c4c71 100644 --- a/agent/redact.py +++ b/agent/redact.py @@ -570,6 +570,11 @@ def redact_sensitive_text( if ":" in text and '"' in text: def _redact_json(m): key, value = m.group(1), m.group(2) + # Same programmatic-env-lookup exception as _redact_env above + # (issue #2852): "apiKey": "os.getenv('X')" is a code snippet, + # not a leaked secret value. + if _ENV_LOOKUP_VALUE_RE.match(value): + return m.group(0) return f'{key}: "{_mask_token(value)}"' text = _JSON_FIELD_RE.sub(_redact_json, text) @@ -579,6 +584,11 @@ def redact_sensitive_text( if ":" in text and "://" not in text: def _redact_yaml(m): key, sep, value = m.group(1), m.group(2), m.group(3) + # Same programmatic-env-lookup exception as _redact_env above + # (issue #2852): api_key: os.getenv('X') is a code snippet, + # not a leaked secret value. + if _ENV_LOOKUP_VALUE_RE.match(value): + return m.group(0) return f"{key}{sep}{_mask_token(value)}" text = _YAML_ASSIGN_RE.sub(_redact_yaml, text) diff --git a/tests/agent/test_redact.py b/tests/agent/test_redact.py index a3ee94e10a3..57956a383b0 100644 --- a/tests/agent/test_redact.py +++ b/tests/agent/test_redact.py @@ -185,6 +185,34 @@ class TestEnvLookupPreserved: result = redact_sensitive_text(text, force=True) assert "os.getenv('HOMEASSISTANT_TOKEN')" in result + def test_json_field_os_getenv_preserved(self): + # _redact_env has the env-lookup exception; _redact_json (a separate + # closure, JSON key: "value" syntax) did not, and mangled this into + # '"apiKey": "os.get...EY')"'. + text = '{"apiKey": "os.getenv(\'OPENAI_API_KEY\')"}' + assert redact_sensitive_text(text, force=True) == text + + def test_json_field_os_environ_get_preserved(self): + text = '{"token": "os.environ.get(\'MY_TOKEN\')"}' + assert redact_sensitive_text(text, force=True) == text + + def test_json_field_real_value_still_redacted(self): + text = '{"apiKey": "sk-realSecretValue1234567890"}' + result = redact_sensitive_text(text, force=True) + assert "sk-realSecretValue1234567890" not in result + + def test_yaml_field_os_getenv_preserved(self): + # Same exception missing from _redact_yaml (unquoted key: value + # syntax) — mangled 'api_key: os.getenv("OPENAI_API_KEY")' into + # 'api_key: os.get...EY")'. + text = 'api_key: os.getenv("OPENAI_API_KEY")' + assert redact_sensitive_text(text, force=True) == text + + def test_yaml_field_real_value_still_redacted(self): + text = "api_key: sk-realSecretValue1234567890" + result = redact_sensitive_text(text, force=True) + assert "sk-realSecretValue1234567890" not in result + class TestJsonFields: def test_json_api_key(self):