393 files

This commit is contained in:
Home Assistant Version Control
2026-09-27 17:16:54 +00:00
parent b05b7a897e
commit 39d97ac2db
404 changed files with 54006 additions and 11034 deletions
@@ -17,6 +17,7 @@ from .const import (
MaintenanceStatus,
TriggerEntityState,
)
from .entity.triggers import normalize_entity_ids
from .helpers.schedule import read_legacy_fields
if TYPE_CHECKING:
@@ -44,8 +45,36 @@ TO_REDACT = {
"mpn",
"gtin",
"storage_location",
# Per-member avatar overrides (#169): user ids with names/initials.
"member_display",
# The HA user an on-complete action runs as (a user UUID, bug audit
# 2026-09-26) — see also _redact_actions for the action's payload.
"configured_by",
}
REDACTED = "**REDACTED**"
def _redact_actions(data: Mapping[str, Any]) -> dict[str, Any]:
"""Redact the free-form payload of every task's on-complete action.
The ``data`` of a service call is whatever the user typed (a message, a
phone number, a URL with a token) — while the service name and target
stay, they are what one debugs. A blanket ``"data"`` key in TO_REDACT
would hit unrelated structures, so this is targeted (bug audit
2026-09-26, SEC-11).
"""
tasks = data.get(CONF_TASKS)
if not isinstance(tasks, dict):
return dict(data)
new_tasks: dict[str, Any] = {}
for task_id, task in tasks.items():
action = task.get("on_complete_action") if isinstance(task, dict) else None
if isinstance(action, dict) and action.get("data"):
task = {**task, "on_complete_action": {**action, "data": REDACTED}}
new_tasks[task_id] = task
return {**data, CONF_TASKS: new_tasks}
async def async_get_config_entry_diagnostics(hass: HomeAssistant, entry: MaintenanceSupporterConfigEntry) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
@@ -53,12 +82,15 @@ async def async_get_config_entry_diagnostics(hass: HomeAssistant, entry: Mainten
diag: dict[str, Any] = {
"entry": {
"title": entry.title,
"unique_id": entry.unique_id,
# An object entry's title IS the object name (redacted in the
# data below) and its unique id is the slug of that name — both
# went out in the clear (bug audit 2026-09-26, SEC-11).
"title": entry.title if is_global else REDACTED,
"unique_id": entry.unique_id if is_global else REDACTED,
"version": entry.version,
"is_global": is_global,
},
"data": async_redact_data(entry.data, TO_REDACT),
"data": async_redact_data(_redact_actions(entry.data), TO_REDACT),
}
if is_global:
@@ -157,20 +189,9 @@ def _check_trigger_status(hass: HomeAssistant, data: Mapping[str, Any]) -> list[
if not trigger_config:
continue
entity_ids: list[str] = list(trigger_config.get("entity_ids", []))
if not entity_ids:
single = trigger_config.get("entity_id")
if single:
entity_ids = [single]
# Compound triggers: collect entity_ids from conditions
if not entity_ids and trigger_config.get("type") == "compound":
for cond in trigger_config.get("conditions", []):
for eid in cond.get("entity_ids", []):
if eid not in entity_ids:
entity_ids.append(eid)
cond_eid = cond.get("entity_id")
if cond_eid and cond_eid not in entity_ids:
entity_ids.append(cond_eid)
# The shared walk (compound conditions incl. their nested
# trigger_config, which this copy missed — DRY audit 2026-09-26 B).
entity_ids = normalize_entity_ids(trigger_config)
if not entity_ids:
continue
@@ -223,7 +244,8 @@ def _check_data_quality(data: Mapping[str, Any]) -> list[str]:
warnings.append(f"Task {task_id} is time-based but has no interval")
trigger = task.get("trigger_config")
if trigger and trigger.get("type") != "compound" and not trigger.get("entity_id"):
# A trigger stored with only the plural entity_ids has an entity too.
if trigger and trigger.get("type") != "compound" and not normalize_entity_ids(trigger):
warnings.append(f"Task {task_id} has trigger config but no entity")
return warnings