fix(redact): skip env-lookup exception for JSON/YAML config field redaction

_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.
This commit is contained in:
srojk34 2026-07-05 17:43:47 +03:00 committed by Teknium
parent 2e2212be1b
commit a573066543
2 changed files with 38 additions and 0 deletions

View file

@ -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)

View file

@ -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):