Skip to content

Devices rows for the same machine are indistinguishable, and Revoke is the only verb #71

Description

@karngyan

What happens

A Devices screen can list several rows that all read Browser on karn.local, with nothing to tell them apart but a paired-at and a last-seen date. Four rows, three of them identical, on a screen whose only action is Revoke.

Why there are several

Not duplication, and not a bug in enrolment. A browser's identity is the X25519 static key in IndexedDB (web/src/crypto/keys.ts:134, record device-static), and IndexedDB is scoped to the origin. So the same browser reaching the same daemon from a different origin is a different device to that daemon, and self-enrols again. web/src/crypto/keys.ts says as much at the top: "'Every origin' is two" — the daemon's own origin and the relay's.

In a development loop it is more than two:

  • http://127.0.0.1:7719 — the daemon direct
  • https://<relay>.workers.dev — the relay
  • http://127.0.0.1:5173 — the vite dev server under make web-dev
  • http://localhost:7719 — a different origin from 127.0.0.1, same daemon

That part is the trust model working as designed and should not change.

Why they look the same

enrolLabel (internal/daemon/fleet.go:266) returns "Browser on " + hostname. Naming the machine is deliberate and the comment above it is worth keeping: the row also appears on other machines' Devices screens, because a browser reaching machine B makes B write a row of its own from the certificate, and any label saying "this machine" would be read on B as a claim about B.

So the label is right and simply does not carry the thing that differs. crypto.Device (internal/crypto/devices.go:19) holds ID, Label, PublicKey, PairedAt, LastSeen and Cert — nothing about where the device connects from.

What to do

Record the origin at enrolment and show it on the row. The daemon has the Origin header on the enrolment POST, so nothing new has to be asked for or trusted.

Points to settle while building it:

  • Origin is a hint, not an identity. It is whatever the browser sent, it is only known for devices that enrolled over loopback, and a device paired by QR has no meaningful one. The row should treat a missing origin as ordinary rather than as suspicious.
  • Does it belong in the certificate, or only in the local row? The label travels the fleet inside the certificate; an origin is a fact about how this machine was reached and probably should not. If it stays local, a row on machine B still cannot be told apart, which may be acceptable.
  • A first-seen origin, or all of them? One device key can legitimately reach a daemon over loopback and later over the relay.
  • Older rows have no origin. They should stay readable and revocable rather than being singled out.
  • Secondary, and possibly the better half of the fix: make the row easier to place regardless — a short key fingerprint, a truer last-seen, and whether the device is connected right now. "Which one is the tab I have open" is the real question at the moment of revoking, and last-seen answers it badly.

Where to look

  • internal/daemon/fleet.goenrolRequest, enrolLabel, the enrolment handler
  • internal/crypto/devices.go — the stored record
  • internal/wire/control.go:492 — what the device list puts on the wire
  • the Devices screen in web/src/routes/

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions