Appearance
Revoke grant
Revoking a Device's grant means flipping the cloud-side Host Link to status: 'revoked'. After revoke, all heartbeats from that Device's bearer token are rejected, and the Device shows status: 'revoked' in devices.list.
This is distinct from disconnect. Disconnect is local cleanup. Revoke is cloud-side authorization invalidation. The two combine under hivecast logout --revoke-cloud-link.
When to revoke
| Situation | Revoke? | Also disconnect locally? |
|---|---|---|
| Device sold or returned | yes | yes (the new owner shouldn't see your stuff) |
| Device lost or stolen | yes | unreachable, but still revoke cloud-side immediately |
| Device suspected compromised | yes | yes — and rotate any shared secrets |
| Decommissioning a runtime / VM | yes | yes |
| "I don't want it linked anymore" but Device is fine | yes (or --local-only if you'll re-pair) | depending on intent |
| Rotating credentials (token expired) | NO — use auth.hostLink.credentials.refresh instead | n/a |
How to revoke
From the Device
bash
node projects/matrix-3/packages/hivecast/bin/hivecast.mjs logout \
--revoke-cloud-link \
--home /tmp/matrix-homeThis calls auth.hostLink.revoke cloud-side, then runs the same local cleanup as a default logout.
From the bus, against the cloud
If you have a signed-in principal session to the cloud:
bash
matrix invoke system.auth auth.hostLink.revoke \
'{"hostLinkId":"hostlink_<id>","principalId":"p_<sha20>"}'Or by hostId:
bash
matrix invoke system.auth auth.hostLink.revoke \
'{"hostId":"host_<id>","principalId":"p_<sha20>"}'The cloud verifies caller identity from transport metadata (per CLAUDE.md Rule 5), confirms the principal owns the link, then sets status: 'revoked', revokedAt: <iso>, updatedAt: <iso>.
From the HiveCast Devices page (target state)
The HiveCast Devices page should expose a "Disconnect device" action per row. The current matrix-web implementation has a first-pass version of this. Clicking it calls auth.hostLink.revoke server-side with the authenticated principal as caller.
Status: the Disconnect action exists in matrix-web but rename, richer first-class physical-device grouping, rotation, and driver / resource summaries are still future launch work. See
WORKSTREAMS/product-launch/HIVECAST-DEVICE-ENROLLMENT-SPEC.md§ "Current implementation status — 2026-04-30".
What revoke changes
Cloud-side, on the Host Link row:
statusflips from'active'to'revoked'revokedAtis set to the current ISO timestampupdatedAtis setheartbeatTokenHashis left as-is, but no longer matters becauseverifyHeartbeatToken()returnsnullwheneverstatus !== 'active'
In system.devices:
- The presence record (if any) is unchanged immediately
- The next
devices.list({ principalId })projection sees the revoked Host Link and emits a view withstatus: 'revoked' compactDeviceViews()keeps a stable-installId record over the revoked one whenincludeOffline: false
In host.control on the still-running Device:
- The next cloud heartbeat returns HTTP 403
device.heartbeat.failedevent is emitted- The Device keeps trying every interval
Idempotency
auth.hostLink.revoke is idempotent. Calling it twice is fine; the second call returns the same record without updating revokedAt.
What revoke does NOT do
- Does NOT delete the Host Link row. The row stays in the cloud state with
status: 'revoked'for audit. A future archive sweep may prune very old revoked rows; that policy is target state. - Does NOT clean the Device's local files. Revoke is a cloud action. Use
hivecast logoutfor local cleanup, or pair the operations ashivecast logout --revoke-cloud-link. - Does NOT propagate to other Devices owned by the same principal. Each Host Link revokes independently.
- Does NOT rotate the principal's NATS account seed. The account-level seed is unaffected. Only the Device's NATS user JWT is invalidated indirectly (because that user is scoped under the principal's account but its JWT will expire on its own TTL; revoking the user JWT explicitly cloud-side is target state).
Re-pair after revoke
A Device whose Host Link was revoked can re-pair the same hostId / installId:
- The Device runs
hivecast logout --local-onlyto clear stale credential files. - The Device runs
hivecast login --deviceagain. - The cloud creates a new Host Link record (
hostlink_<new id>) for the samehostId. The previous revoked record stays in thehostLinks[]history.
Recovery: revoke without ability to reach the Device
If you can't reach the Device (lost laptop, dead VM):
- Revoke from the bus or the HiveCast UI as above.
- The Device's bearer token is now useless cloud-side.
- Optionally, re-pair from a new Device — the cloud will mint fresh credentials for whatever new
hostIdthe new install reports. - The old Device's local credential files are now orphans. Whoever recovers the Device should run
hivecast logout --all(or just reformat — it's a Device they no longer trust).
Audit and observability
HostLinkStore.revoke() only flips the row. Auditing today happens via read-back: comparing auth.hostLink.list({ principalId }) snapshots over time tells you which Devices were revoked when.
A target-state addition would be an explicit audit log entry on every revoke. See Security / Auditing in the docs-security package for the broader audit story.
See also
- Disconnect — local cleanup; pairs with revoke.
- Rotate credentials — refresh, not revoke.
- Inventory / Online / offline status — what
revokedlooks like in the view.
Source:
projects/matrix-3/packages/system-auth/src/host-auth.ts(HostLinkStore.revoke()at lines 951-962,verifyHeartbeatToken()reject path at lines 936-938).