Commit 254c515
fix: reject dangling registry references across all registry write tools (#2162)
* fix: generalize registry-reference validation to every registry kind
`validate_registry_ids` only covered area_id, labels, and a single
"helpers"-scoped category, and failed open on every lookup but the area
one. Issue #2159 reproduced dangling references on the other surfaces, so
the validator now takes a scope->category_id mapping plus floor_id, and a
single `fail_closed` flag applies to every requested lookup: a registry
that cannot be read rejects the write with CONNECTION_FAILED instead of
waving the reference through.
The two helper call sites move to the new signature and opt into
fail-closed — a degraded lookup must never allow a dangling reference.
Per-registry checks are extracted so the entry point stays well inside
the C901 limit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: reject dangling registry references across registry write tools
Issue #2159: every remaining write tool that takes a cross-registry
reference forwarded it to Home Assistant unchecked, and HA stores an
unknown ID verbatim, so the tool reported success while leaving the
registry pointing at something that does not exist.
- ha_set_entity: one preflight at the tool entry now covers area_id,
labels, and categories for both the single and bulk paths, replacing
the per-entity area check that bulk would have multiplied by N.
Labels are only validated for label_operation set/add — removing an
unknown label is the cleanup path for these dangling references and
must stay possible.
- ha_set_device: area_id and the replacement label set are validated
before config/device_registry/update.
- ha_set_area_or_floor: floor_id is validated before the area create or
update.
- ha_config_set_automation / ha_config_set_script: the category is
applied post-upsert by apply_entity_category, so both category
sources (the parameter and the config dict) are validated at tool
entry — nothing is created when the category does not exist.
- ha_config_set_scene: its private category validator now delegates to
the shared one so all four tools reject dangling references the same
way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: cover registry-reference validation on every write surface
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: validate single-entity refs at the write site; honor script transform category
Round 1 CI + bot review feedback:
- Single-entity validation moved into _update_single_entity immediately
before the registry write (restores the #2160 check-to-write window,
flagged by Codex); bulk keeps one entry preflight via preflighted=True.
- ha_config_set_script python_transform now honors category: popped from
the transformed config, validated fail-closed, applied post-upsert
(parity with automations, flagged by CodeRabbit).
- Tests adapted: e2e device-labels test now creates its labels first;
helper fail-open test flipped to a type-keyed mock; bulk/add mock
chains reordered for the new call order.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: ruff format
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: restore original device labels in e2e cleanup
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: address second-round review — category recheck, script rename resolution, edge hardening
- apply_entity_category rechecks the scope registry immediately before
the write; a category deleted during the upsert/wait gap degrades to a
warning instead of a dangling reference (Codex).
- ha_config_set_script resolves the storage key to the current entity_id
before category application and registration wait — a registry-renamed
script no longer sends the category to a nonexistent entity (Codex).
- Empty-string elements inside a labels list are rejected as unknown IDs
instead of riding the write (Codex).
- Fail-closed lookup errors preserve their classification: auth failures
surface as AUTH_*, not CONNECTION_FAILED (Codex).
- e2e device-labels test creates its labels inside the cleanup guard so a
failed second create cannot leak the first (Codex).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: classify auth failures on both WS channels; pin flow-helper fail-closed and scan fallback; gate script resolution
Maintainer review (three concerns):
- The connection manager now carries connect()'s exception object and
re-raises an auth failure as HomeAssistantAuthError instead of
collapsing it into the connection error; the failure-envelope channel
discriminates on HA's preserved error_code (unauthorized -> AUTH_*),
mirroring _resolve_device_id_for_entity. Both shapes pinned.
- Pinned the previously untested behaviors: fail_closed=True at the
_handle_flow_helper call site and the registry-list rename fallback in
_resolve_script_entity_id (component-less installs).
- _commit_script_config resolves the entity id only when wait or a
category consumes it; the wait=False bulk path skips the lookups.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: pin zero WebSocket calls on the ungated script write path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: classify acquisition-phase auth failures via the preserved cause chain
The transport wrap re-raises acquisition failures as
HomeAssistantConnectionError 'from e' (phase-before-type, deliberately
pinned), so the manager's auth raise never reached the caller as an auth
class. The fail-closed branch now walks the __cause__ chain — mirroring
its error_code discrimination — and the pin runs through the REAL
send_websocket_message as the review required. The mocked direct-raise
test is replaced with the wrapped-cause shape the client actually
produces.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: isolate the real-transport auth test from global settings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent 956d8a9 commit 254c515
22 files changed
Lines changed: 1853 additions & 284 deletions
File tree
- src/ha_mcp
- client
- tools
- tests/src
- e2e/workflows
- areas
- entities
- registry
- unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
292 | 292 | | |
293 | 293 | | |
294 | 294 | | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
295 | 301 | | |
296 | 302 | | |
297 | 303 | | |
| |||
317 | 323 | | |
318 | 324 | | |
319 | 325 | | |
| 326 | + | |
320 | 327 | | |
321 | 328 | | |
322 | 329 | | |
| |||
383 | 390 | | |
384 | 391 | | |
385 | 392 | | |
| 393 | + | |
386 | 394 | | |
387 | 395 | | |
388 | 396 | | |
| |||
1037 | 1045 | | |
1038 | 1046 | | |
1039 | 1047 | | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
1040 | 1058 | | |
1041 | 1059 | | |
1042 | 1060 | | |
| |||
1295 | 1313 | | |
1296 | 1314 | | |
1297 | 1315 | | |
| 1316 | + | |
| 1317 | + | |
| 1318 | + | |
| 1319 | + | |
| 1320 | + | |
| 1321 | + | |
| 1322 | + | |
1298 | 1323 | | |
1299 | 1324 | | |
1300 | 1325 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
675 | 676 | | |
676 | 677 | | |
677 | 678 | | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
678 | 687 | | |
679 | 688 | | |
680 | 689 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| |||
852 | 853 | | |
853 | 854 | | |
854 | 855 | | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
855 | 867 | | |
856 | 868 | | |
857 | 869 | | |
| |||
1017 | 1029 | | |
1018 | 1030 | | |
1019 | 1031 | | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
1020 | 1042 | | |
1021 | 1043 | | |
1022 | 1044 | | |
| |||
0 commit comments