Post-release deep review of the merged HOMECORE platform PR (#1451)
turned up several real issues, fixed here:
- homecore-hap: StoredAccessory's permanent Ed25519 signing seed and
StoredSetup's SRP salt/verifier were reachable via derived Debug.
Not actively triggered by any current code path, but a future
logging/panic-message change (an ordinary thing to add) would have
printed the accessory's compromise-forever identity key in
plaintext -- there's no rotation mechanism. Added manual, redacted
Debug impls matching the existing SetupCode pattern; StoreState/
PairingStore's derived Debug inherits the redaction automatically.
New test pins the exact rendered output.
- homecore-api: /api/history/period and /api/logbook rejected the
default (no filter_entity_id/entity) call shape once a room had
more than 32 known entities -- exactly how the real HA frontend
calls these endpoints. The MAX_HISTORY_ENTITIES cap now only
applies to an explicit, unusually-large filter list; the existing
MAX_API_HISTORY_ROWS total-row budget already bounds the actual
work regardless of entity count. Two new tests: unfiltered succeeds
with 40 known entities, explicit oversized filter is still rejected.
- homecore-api: fire_event (both REST and WS) restricted event_type
to [a-z0-9_]+, but real HA integrations commonly fire mixed-case,
dotted, or hyphenated types (mobile_app.notification_action,
ios.action_fired). Factored the check into a single
is_valid_event_type() shared by both transports, relaxed to what
actually matters for server safety: non-empty, length-bounded, no
control characters.
- homecore-migrate: write_config_entries/write_device_registry/
write_entity_registry's atomic no-clobber write had no escape
hatch -- an operator who fixed a bad source row (or wanted a fresh
re-import) had to manually delete prior output first. Added a
--force CLI flag (default off, preserving the existing no-clobber
default and its explicit malformed_entry_is_an_error_not_a_partial_write
test) that atomically replaces an existing destination. First
attempt used bare fs::rename, which hit real ERROR_ACCESS_DENIED
sharing-violation flakiness on Windows; switched to pre-clearing
the destination then publishing through the same hard_link step
the default path already uses reliably.
All touched crates re-verified: homecore-hap 46 tests (was 45),
homecore-api 20+6+6+7=39 tests (was 18+6+6+7=37), homecore-migrate 25
tests (was 24), all 0 failed, clippy clean under -D warnings.
Also corrected the public v2051 GitHub release notes: "Wasmtime
36.0.12 component loading" was inaccurate (the crate uses the
core-module API with a hand-rolled host ABI, not the Component
Model/WIT) -- doc-only, not a code change.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(homecore-api security): auth-gate GET /api/ (HC-API-AUTH-01, ADR-161)
`rest::api_root` took no headers and unconditionally returned
`200 {"message":"API running."}`, while every sibling REST route gates
on `BearerAuth::from_headers`. HA's `APIStatusView` inherits
`requires_auth = True`, so `/api/` must return 401 for a missing/wrong
bearer — HA clients use it as a token-validation probe, so a 200 told a
bad-token client its token was valid and let an unauthenticated party
confirm a live endpoint. LOW severity (static body, no data leak),
reported at true severity.
Fix: `api_root(headers, State)` validates the bearer like `get_config`.
Pinned by fails-on-old tests (200 -> assert 401):
- api_root_rejects_missing_bearer
- api_root_rejects_wrong_bearer
guarded by api_root_accepts_correct_bearer (still 200 with valid token).
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(homecore-api security): recover WS subscription on broadcast lag (HC-WS-LAG-01, ADR-161)
`subscribe_events`'s per-subscription task matched `Err(_) => break` on
both broadcast `recv()` arms. `RecvError::Lagged(n)` (a slow consumer
falling >EVENT_CHANNEL_CAPACITY=4,096 events behind) is recoverable —
the bus doc says "Lagged receivers must re-sync" and HA keeps the
subscription alive across a lag. The old code treated the first lag as
fatal, so after an event burst the client's stream went permanently
silent with no error frame — a self-inflicted event-delivery DoS under
load. LOW severity.
Fix: `Lagged(_) => continue` (skip dropped window, re-sync),
`Closed => break`, on both the system and domain arms.
Pinned by subscription_survives_broadcast_lag: subscribes, floods 6,000
filtered events past the 4,096 capacity to force a Lagged, then asserts
a subsequent subscribed event is still delivered (old code: 5s timeout).
Co-Authored-By: claude-flow <ruv@ruv.net>
* docs(homecore-api security): record HC-API-AUTH-01 + HC-WS-LAG-01 review (ADR-161)
CHANGELOG [Unreleased] Security entry + ADR-161 addendum documenting the
beyond-SOTA network-API review: two LOW bugs fixed (unauthenticated
GET /api/; WS subscription killed on broadcast lag) and the
auth/traversal/injection/info-leak/CORS dimensions confirmed clean with
evidence (no traversal surface — in-memory DashMap + EntityId allowlist;
HashSet token compare, not a byte-== timing oracle).
Co-Authored-By: claude-flow <ruv@ruv.net>
A1 (CRITICAL): the /api/websocket handshake accepted any non-empty token,
ignoring the LongLivedTokenStore whitelist the REST path enforces — a full
WS auth bypass. Now validates via state.tokens().is_valid() before auth_ok;
wrong tokens get auth_invalid + close.
A2 (HIGH): WS command replies were pushed into an mpsc whose only consumer
logged and discarded them — no result/pong/event reached the client. Split
the socket with futures StreamExt::split; a dedicated writer task drains the
response channel onto the wire.
A8 (HIGH): the homecore-api dev bin bound 0.0.0.0 with unconditional
allow-any auth and no env path. Wired the HOMECORE_TOKENS env path (dev
fallback warn-logged when unset) and defaulted the bind to 127.0.0.1
(HOMECORE_BIND to opt into LAN).
Tests (fail on old source):
- ws_handshake::wrong_token_is_rejected (old → auth_ok)
- ws_handshake::result_reply_is_received / ping_pong_reply_is_received (old → timeout)
- server_bin_auth::provisioned_bin_rejects_wrong_bearer / from_env_path_enforces_whitelist
Co-Authored-By: claude-flow <ruv@ruv.net>