Skip to content

Device pairing for the console: a verifier on keel's disk, the secret on the device — blocked on #648 #656

Description

@eaitbrahim

Option 1 of #634, which #634 shipped without and named as the right answer in the wrong order.

What #634 left open

#634 fixed the case where the browser closed and keel serve kept running: the session cookie now carries a Max-Age, so a token that is still valid stops being thrown away. It deliberately did not fix the case where keel serve restarts, because that one is a theorem rather than a design choice — after a restart, no client can present a credential the new run accepts unless the server persisted something across the restart.

What ships today for that case is honesty: the console distinguishes a refusal from an outage, and offers a field to paste the address (or just the token) keel printed. That works from a browser tab and from an installed app whose shell is still cached.

The tail it does not reach, recorded in #634's commit and repeated here so it is not lost: an installed app whose service-worker cache has been evicted — Safari does this after seven unused days — makes a tokenless network navigation and gets the server's plain-text refusal in a window with no address bar. The text is truthful and names the token; there is nowhere in that window to paste one. That is the case pairing exists for.

Why it was not built at #634

#648 is the declared prerequisite for every Phase B item and says in terms that nothing remote ships before it. A pairing credential's whole value is remote — it is what lets a phone on a mesh re-authorise with no operator in the loop. Shipping a long-lived, remotely-usable bearer secret before the pass that defines token issuance, expiry and brute-force posture off loopback is shipping the prize before the lock.

And it would change what keel serve --host warns about. Today: "The session token is the only thing in the way, and it travels in cleartext" — and that token dies with the process. A pairing secret in cleartext on a LAN is a credential that does not. Inside a WireGuard mesh that is fine; on a plain --host LAN it is a real downgrade, and #648 is where the difference between those two gets written down.

The shape to build, so the design is not re-derived

Acceptance

  • An installed PWA whose worker cache has been evicted, launched after a keel serve restart, reaches a usable state with no terminal and no address bar.
  • Reading keel's state directory yields no value that authenticates as a device.
  • keel devices lists and revokes; revocation takes effect on the next request.
  • The disjointness pin passes and no capability row is added.
  • keel link prerequisite: the remote-exposure security pass — bind opt-in, tunnel Host validation, token issuance off loopback #648 has landed, and this is written against the token posture that pass defines — not around it.
  • The --host warning is re-read against what this adds and is still true, or is rewritten to stay true.

Blocked on #648. Follows #634.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions