cv.sensitive cannot mask int-valued secrets; openthread network_key goes plaintext when the legacy heuristic is removed in 2026.12.0
The problem
SensitiveValidator.__call__ only tags string results, so cv.sensitive(...) is a silent no-op on any field whose validator returns an int — it validates fine and masks nothing:
# esphome/config_validation.py:550-556
validated = self.inner(value)
# Tag string results so yaml_util.dump can mask them. Non-string
# results pass through unchanged; ...
if isinstance(validated, str) and not isinstance(validated, SensitiveStr):
return SensitiveStr(validated)
return validatedToday that's masked by _redact_with_legacy_fallback in __main__.py, which works on the dumped text and so catches ints too. But that fallback is removed in 2026.12.0 (_LEGACY_REDACTION_REMOVAL), and the migration it advertises — "Mark this field's schema validator with cv.sensitive(...)" — cannot help these fields. They lose masking with no available replacement:
| Field | Validator | |
|---|---|---|
openthread → network_key |
cv.hex_int (components/openthread/__init__.py:166) |
the Thread network key — joins the mesh |
fingerprint_grow → password |
cv.uint32_t (components/fingerprint_grow/__init__.py:93) |
sensor access password |
fingerprint_grow → new_password |
cv.uint32_t (components/fingerprint_grow/__init__.py:94) |
network_key is the one that worries me: it's the credential for joining the Thread network, and after December esphome config prints it in full — including in logs and pastes in this tracker.
Reproduce
Any openthread: config with a network_key: set, on 2026.10.0-dev:
esphome config thread.yaml | grep network_key
network_key: \033[8m...\033[28m # today: masked by the legacy fallbackWrapping the schema in cv.sensitive(cv.hex_int) changes nothing about that output — the value is an int by the time SensitiveValidator sees it, so it is returned untagged and dump() has nothing to conceal. After 2026.12.0 removes the fallback, the value prints in the clear.
Possible directions
I don't have a view on which is right for the codebase:
- A
SensitiveInt-style marker alongsideSensitiveStr(int subclass, same representer hook). - Tag at dump time from the schema rather than from the value, so masking doesn't depend on the validated type at all.
- Have these specific validators return strings, which seems worse — it changes codegen for
network_keyand the fingerprint passwords.
Which version of ESPHome has the issue?
2026.10.0-dev (also 2026.9.0)
What type of installation are you using?
pip
Component causing the issue
config_validation / openthread, fingerprint_grow
Additional information
Found while auditing every field the legacy heuristic matches for esphome/esphome#19387, which marks the three remaining string-valued secrets. These int-valued ones are the leftovers that annotation can't reach, so I split them out rather than shipping a no-op wrapper that would look like coverage.
Happy to implement whichever direction you prefer.
Source: esphome/esphome