Files
ruvnet--RuView/v2/crates/homecore-hap
ruv c88b7a12d8 fix(homecore): review findings from PR #1451 — HAP secret redaction, REST history/logbook cap, event_type validation, migration --force
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>
2026-07-27 16:15:56 -04:00
..

homecore-hap

homecore-hap is HOMECORE's bounded, fail-closed HAP IP accessory server (ADR-125). It implements the HAP R2 cryptographic pairing and transport boundary without relying on the broken hap 0.1 pre-release crate.

Security and protocol coverage

  • Pair-Setup M1-M6 uses the RFC 5054 3072-bit group with SHA-512, the HAP compatibility proof construction, HKDF-SHA512, ChaCha20-Poly1305, and Ed25519 long-term keys.
  • Pair-Verify M1-M4 uses ephemeral X25519, strict Ed25519 transcript verification, HKDF-SHA512, and authenticated encrypted sub-TLVs.
  • After successful M4, all HTTP and EVENT/1.0 traffic uses HAP records: two-byte little-endian authenticated lengths, at most 1024 plaintext bytes, independent directional keys, and monotonic 64-bit nonces. Authentication, replay, truncation, and oversize failures close the connection without an oracle response.
  • /accessories, /characteristics, and /pairings are inaccessible until Pair-Verify succeeds on that TCP connection. Pairings add/remove/list is restricted to a currently persisted administrator.
  • Accessory identity, Ed25519 seed, SRP salt/verifier, and controller records are stored in a versioned file using same-directory atomic replacement. Created Unix directories use mode 0700, files use 0600, and permissive, oversized, symlinked, legacy, or malformed stores fail closed.
  • The raw setup code is returned only during first provisioning. Only its SRP verifier is persisted; SetupCode redacts Debug output and zeroizes on drop.
  • Removing the last administrator atomically clears every pairing. Live sessions observe pairing revisions and are revoked, while the removal response is delivered before the requesting session closes.

The server also bounds connections, headers, bodies, request time, shutdown, TLV sizes, controller counts, setup attempts, and concurrent Pair-Setup. mDNS advertises the persisted accessory identifier and updates sf after pairing or unpairing.

Provisioning and server integration

use std::{net::IpAddr, sync::Arc};
use homecore_hap::{
    start_server, HapBridge, HapServerConfig, HapServiceRecord,
    MdnsSdAdvertiser, PairingStore,
};

# async fn run() -> Result<(), Box<dyn std::error::Error>> {
let provisioned = PairingStore::load_or_create(
    "/var/lib/homecore-hap/security.json",
)?;
if let Some(setup_code) = provisioned.setup_code.as_ref() {
    // Send this once to a trusted local display or provisioning boundary.
    println!("HAP setup code: {}", setup_code.expose());
}
let pairings = Arc::new(provisioned.store);
let record = HapServiceRecord::bridge(
    "HOMECORE Bridge",
    51826,
    pairings.accessory_id()?,
);
let bridge = HapBridge::new(record);
let advertiser = Arc::new(MdnsSdAdvertiser::new(
    "homecore",
    "192.168.1.50".parse::<IpAddr>()?,
)?);

let server = start_server(
    HapServerConfig::default(),
    bridge.clone(),
    pairings,
    advertiser,
).await?;

// Feed HOMECORE StateChanged events through bridge.update_accessory(...).
server.shutdown().await?;
# Ok(())
# }

The mDNS device ID must equal the persisted accessory ID; startup rejects a mismatch. Real mDNS also requires a LAN-routable advertised address and multicast access.

Validation and remaining interoperability limits

The deterministic suite covers the HAP SRP session-key vector, complete Pair-Setup and Pair-Verify ceremonies, transcript tampering, wrong proofs, malformed/replayed/oversized records, atomic restart, last-admin removal, and a real TCP lifecycle from Pair-Verify through encrypted /accessories.

This is protocol-level HAP R2 coverage, not a claim of Apple certification:

  • It has not yet been exercised against a current Apple Home controller or the current commercial MFi specification.
  • Transient and split Pair-Setup flags are rejected as Unavailable.
  • Writable characteristic service calls, timed writes, resource endpoints, and stable persisted AID/IID allocation are not implemented. The present characteristic surface is read and event subscription only.
  • Operational hardening still depends on protecting the host and the 0600 security file; no hardware-backed key store is integrated.

Build and test:

cargo test -p homecore-hap --no-default-features
cargo test -p homecore-hap --features hap-server
cargo clippy -p homecore-hap --all-targets --features hap-server -- -D warnings

Decisions