mirror of
https://github.com/esphome/esphome.git
synced 2026-10-06 19:06:37 +00:00
[core] Sensitive redaction via yaml_util representer (#16690)
This commit is contained in:
@@ -15,6 +15,7 @@ from esphome.yaml_util import (
|
||||
DiscoveredYamlFiles,
|
||||
ESPHomeDataBase,
|
||||
ESPLiteralValue,
|
||||
SensitiveStr,
|
||||
discover_user_yaml_files,
|
||||
force_load_include_files,
|
||||
format_path,
|
||||
@@ -1340,3 +1341,57 @@ def test_frontmatter_included_file_stored(tmp_path: Path) -> None:
|
||||
assert main.resolve() not in core.CORE.frontmatter
|
||||
# Included file's frontmatter is captured
|
||||
assert core.CORE.frontmatter[inc.resolve()]["child_meta"] == "hello"
|
||||
|
||||
|
||||
def test_sensitive_str__is_a_str_subclass() -> None:
|
||||
value = SensitiveStr("hunter2")
|
||||
assert isinstance(value, str)
|
||||
assert value == "hunter2"
|
||||
|
||||
|
||||
def test_dump__redacts_sensitive_str_by_default() -> None:
|
||||
out = yaml_util.dump({"password": SensitiveStr("hunter2")})
|
||||
assert "\\033[8mhunter2\\033[28m" in out
|
||||
assert "hunter2" not in out.replace(
|
||||
"\\033[8mhunter2\\033[28m", ""
|
||||
) # the raw value is only present inside the wrap
|
||||
|
||||
|
||||
def test_dump__show_secrets_emits_sensitive_str_raw() -> None:
|
||||
out = yaml_util.dump({"password": SensitiveStr("hunter2")}, show_secrets=True)
|
||||
assert "hunter2" in out
|
||||
assert "\\033[8m" not in out
|
||||
assert "\\033[28m" not in out
|
||||
|
||||
|
||||
def test_dump__plain_str_is_not_redacted() -> None:
|
||||
out = yaml_util.dump({"hostname": "myserver"})
|
||||
assert "myserver" in out
|
||||
assert "\\033[8m" not in out
|
||||
|
||||
|
||||
def test_dump__secret_reference_wins_over_redaction() -> None:
|
||||
# If the value also has an entry in _SECRET_VALUES (i.e., it was loaded
|
||||
# via !secret), the dump should render it as !secret <name>, not as a
|
||||
# redacted scalar. SensitiveStr layered on top must not change that.
|
||||
value = SensitiveStr("hunter2")
|
||||
yaml_util._SECRET_VALUES[str(value)] = "my_secret_name"
|
||||
try:
|
||||
out = yaml_util.dump({"password": value})
|
||||
assert "!secret" in out
|
||||
assert "my_secret_name" in out
|
||||
assert "\\033[8m" not in out
|
||||
finally:
|
||||
yaml_util._SECRET_VALUES.clear()
|
||||
|
||||
|
||||
def test_dump__redaction_flag_does_not_leak_between_calls() -> None:
|
||||
# Per-call _Dumper subclass means show_secrets in one call doesn't
|
||||
# affect another. Run them in both orders to catch any leakage.
|
||||
redacted = yaml_util.dump({"password": SensitiveStr("hunter2")})
|
||||
raw = yaml_util.dump({"password": SensitiveStr("hunter2")}, show_secrets=True)
|
||||
redacted_again = yaml_util.dump({"password": SensitiveStr("hunter2")})
|
||||
|
||||
assert "\\033[8m" in redacted
|
||||
assert "\\033[8m" not in raw
|
||||
assert "\\033[8m" in redacted_again
|
||||
|
||||
Reference in New Issue
Block a user