[core] Keep legacy redaction regex as deprecation fallback

Restore the substring regex as a second pass in command_config so
sensitive-shaped fields that haven't been tagged with cv.sensitive(...)
yet are still redacted. Each unique unmarked field name caught by the
heuristic emits a one-time deprecation warning naming the field and the
fix; the fallback itself is slated for removal in 2026.12.0.
This commit is contained in:
J. Nick Koston
2026-05-26 21:55:35 -05:00
parent 23df0229a2
commit 2e53a226a3
2 changed files with 82 additions and 0 deletions
+47
View File
@@ -22,6 +22,7 @@ from esphome.__main__ import (
Purpose,
_get_configured_xtal_freq,
_make_crystal_freq_callback,
_redact_with_legacy_fallback,
_resolve_network_devices,
_validate_bootloader_binary,
_validate_partition_table_binary,
@@ -340,6 +341,52 @@ def mock_ram_strings_analyzer() -> Generator[Mock]:
yield mock_class
def test_redact_with_legacy_fallback__wraps_unmarked_field(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Unmarked sensitive-shaped fields are redacted; a deprecation warning
is emitted naming the field."""
with caplog.at_level(logging.WARNING, logger="esphome.__main__"):
out = _redact_with_legacy_fallback("password: hunter2\n")
assert "password: \\033[8mhunter2\\033[28m" in out
assert any(
"password" in rec.message and "cv.sensitive" in rec.message
for rec in caplog.records
)
def test_redact_with_legacy_fallback__skips_already_wrapped(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Values already wrapped by the SensitiveStr representer don't trigger
the heuristic or the warning."""
wrapped = "password: \\033[8mhunter2\\033[28m\n"
with caplog.at_level(logging.WARNING, logger="esphome.__main__"):
out = _redact_with_legacy_fallback(wrapped)
assert out == wrapped
assert not any("legacy substring" in rec.message for rec in caplog.records)
def test_redact_with_legacy_fallback__captures_full_field_name(
caplog: pytest.LogCaptureFixture,
) -> None:
"""The warning names the actual field, not just the matched fragment."""
with caplog.at_level(logging.WARNING, logger="esphome.__main__"):
_redact_with_legacy_fallback("encryption_key: abc\n")
assert any("encryption_key" in rec.message for rec in caplog.records)
def test_redact_with_legacy_fallback__deduplicates_warnings(
caplog: pytest.LogCaptureFixture,
) -> None:
"""One warning per unique field name even if it appears many times."""
text = "password: a\npassword: b\npassword: c\n"
with caplog.at_level(logging.WARNING, logger="esphome.__main__"):
_redact_with_legacy_fallback(text)
password_warnings = [rec for rec in caplog.records if "'password'" in rec.message]
assert len(password_warnings) == 1
def test_choose_upload_log_host_with_string_default() -> None:
"""Test with a single string default device."""
setup_core()