Skip to content

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

SituationRevoke?Also disconnect locally?
Device sold or returnedyesyes (the new owner shouldn't see your stuff)
Device lost or stolenyesunreachable, but still revoke cloud-side immediately
Device suspected compromisedyesyes — and rotate any shared secrets
Decommissioning a runtime / VMyesyes
"I don't want it linked anymore" but Device is fineyes (or --local-only if you'll re-pair)depending on intent
Rotating credentials (token expired)NO — use auth.hostLink.credentials.refresh insteadn/a

How to revoke

From the Device

bash
node projects/matrix-3/packages/hivecast/bin/hivecast.mjs logout \
  --revoke-cloud-link \
  --home /tmp/matrix-home

This 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:

  • status flips from 'active' to 'revoked'
  • revokedAt is set to the current ISO timestamp
  • updatedAt is set
  • heartbeatTokenHash is left as-is, but no longer matters because verifyHeartbeatToken() returns null whenever status !== '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 with status: 'revoked'
  • compactDeviceViews() keeps a stable-installId record over the revoked one when includeOffline: false

In host.control on the still-running Device:

  • The next cloud heartbeat returns HTTP 403
  • device.heartbeat.failed event 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 logout for local cleanup, or pair the operations as hivecast 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:

  1. The Device runs hivecast logout --local-only to clear stale credential files.
  2. The Device runs hivecast login --device again.
  3. The cloud creates a new Host Link record (hostlink_<new id>) for the same hostId. The previous revoked record stays in the hostLinks[] history.

Recovery: revoke without ability to reach the Device

If you can't reach the Device (lost laptop, dead VM):

  1. Revoke from the bus or the HiveCast UI as above.
  2. The Device's bearer token is now useless cloud-side.
  3. Optionally, re-pair from a new Device — the cloud will mint fresh credentials for whatever new hostId the new install reports.
  4. 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

Source: projects/matrix-3/packages/system-auth/src/host-auth.ts (HostLinkStore.revoke() at lines 951-962, verifyHeartbeatToken() reject path at lines 936-938).