diff --git a/esphome/__main__.py b/esphome/__main__.py index d62fa1bafd..7049f8025e 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -1427,9 +1427,12 @@ def command_config(args: ArgsProtocol, config: ConfigType) -> int | None: # in 2026.12.0 once canonical sensitive fields are migrated. # # The negative lookahead ``(?!\\033\[8m)`` skips values already wrapped by the -# SensitiveStr representer, so explicit tagging silences the warning. +# SensitiveStr representer, so explicit tagging silences the warning. No +# trailing ``\w*`` after the fragment so the fragment must end the field +# name (preserves the prior regex's matching scope while letting the +# leading ``\w*`` capture the full field name for the warning). _LEGACY_REDACTION_RE = re.compile( - r"(?P\w*(?:password|key|psk|ssid)\w*)\: (?!\\033\[8m)(?P.+)" + r"(?P\w*(?:password|key|psk|ssid))\: (?!\\033\[8m)(?P.+)" ) _LEGACY_REDACTION_REMOVAL = "2026.12.0" diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index a103f14da5..ef8d922a8e 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -387,6 +387,18 @@ def test_redact_with_legacy_fallback__deduplicates_warnings( assert len(password_warnings) == 1 +def test_redact_with_legacy_fallback__does_not_match_fragment_in_middle( + caplog: pytest.LogCaptureFixture, +) -> None: + """Fragment must end the field name; embedded matches like + ``key_value_pair`` are unrelated to a sensitive key and must not be + redacted (matching the prior regex's scope).""" + with caplog.at_level(logging.WARNING, logger="esphome.__main__"): + out = _redact_with_legacy_fallback("key_value_pair: abc\n") + assert "\\033[8m" not in out + assert not any("legacy substring" in rec.message for rec in caplog.records) + + def test_choose_upload_log_host_with_string_default() -> None: """Test with a single string default device.""" setup_core()