Skip to content

Pairing integrity: request_id + timestamps on UI; --force supersede #224

@hanwencheng

Description

@hanwencheng

Context

An operator approving a §10.2 agent pairing in the parent-control web UI cannot cross-verify the pending card against the agent machine — a confused-deputy / approve-the-wrong-request surface. Surfaced live (Path A): two stale Pairing request · hermes cards showed PAIR-CODE values (bPe5Y8qNAd…, _rZYUUvRdU…) the operator never saw on the agent, which had displayed pairing codes e3iCqoc… / _QxjoWNP…. There's no way to confirm the request being approved is the one the agent actually opened.

Root cause (grounded)

  • The card's "PAIR-CODE" is the truncated REQUEST_ID, not the agent's code — pending_binding_to_request sets "pairCode": short(&request_id) (crates/agentkeys-daemon/src/ui_bridge.rs:2016); the comment notes the one-time pairing code is consumed at claim and not retained by the broker, so request_id is the only stable post-claim handle — but it's mislabeled + truncated.
  • The card shows no full request_id, no start time, no expiry/countdown.
  • The agent's agentkeys-daemon --request-pairing displays the pairing code + device + expires_at but NOT the request_id (it's only in the 0600 ~/.agentkeys/pairing-request-*.json state file) — so the operator can't read the request_id off the agent to compare even if the master showed it.
  • --request-pairing --force opens a NEW broker request without canceling the device's prior open requests → they accumulate (real incident: 2 duplicate pending bindings for the same device 0xe98a6e82…, differing only by request_id, because the earlier register-502 bug never cleared them).

Scope

  • Agent (--request-pairing): prominently DISPLAY the request_id alongside the pairing code + device pubkey + expiry, so the operator can read request_id + device off the agent screen.
  • Broker: the pending-binding row must carry created_at + expires_at and expose the request_id; keep them for the pending list.
  • Daemon (/v1/agent/pairing/pendingpending_binding_to_request): map the full request_id + created_at + expires_at into the PairingRequest JSON; stop labeling request_id as pairCode.
  • Frontend (apps/parent-control pairing card, app/_components/pairing.tsx): show the full request_id, start time + live expiry countdown, and the full device pubkey — clearly labeled. The operator verifies request_id + device match the agent before accept · Touch ID. Remove/relabel the misleading "PAIR-CODE" field (it's the request_id, not the agent's one-time code).
  • --force supersede: a new --request-pairing (esp. --force) must cancel/supersede the device's prior OPEN broker requests (or the broker dedupes pending bindings per device) so repeated requests replace rather than accumulate.

Acceptance

  • The master pairing card shows the full request_id + start + expiry + full device pubkey, all matching what the agent displays — an operator can cross-verify and refuse an unrecognized request.
  • agentkeys-daemon --request-pairing --force (or run N times) leaves exactly one open request for that device on the broker; the master pending list shows no duplicates.

Out of scope

  • The browser passkey→UserOp wiring (E7 frontend item) · the on-chain registerAgentDevice itself.

Effort

~L (cross-stack: agent output + broker pending-binding fields + daemon mapping + frontend card + --force supersede on the broker).

References

  • arch.md §10.2 (agent-initiated pairing, Matter/HomeKit model)
  • crates/agentkeys-daemon/src/ui_bridge.rs (pending_binding_to_request:1966, list_pairing_requests, register_pairing) · crates/agentkeys-daemon/src/main.rs (run_request_pairing)
  • apps/parent-control/app/_components/pairing.tsx
  • Surfaced while testing Path A in docs/operator-runbook-wire.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/uiParent-control UI, vendor onboarding portal, audit dashboard

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions