Appearance
Rotate credentials
Rotation is replacing a Device's authentication material without breaking its identity. Two artefacts can be rotated:
- The device-scoped NATS user JWT (24h TTL)
- The heartbeat bearer token
Rotating these is normal hygiene. It is also the right answer when an existing token is suspected leaked, when a long-lived Device crosses the JWT TTL, or when the cloud credential store changes (e.g. account seed rotated).
What auth.hostLink.credentials.refresh does
The op is declared in system-auth/src/index.ts:266-271:
auth.hostLink.credentials.refresh
hostLinkId?: string
hostId?: string
principalId: stringEffect:
HostLinkStore.refresh()bumpsupdatedAt/lastRefreshedAt.HostLinkStore.rotateHeartbeatToken()mints a new bearer token, stores its SHA-256 hash, returns the plaintext exactly once.@open-matrix/nats-authmints a fresh device-scoped NATS user JWT + nkey seed, scoped to the link'sauthorityRoot, expiring in 24h.- The new credentials are returned in a
DeviceSetupExchangeResponse-shaped payload (same shape as redeem).
The old NATS user JWT continues to work until its exp claim is reached (NATS does not eagerly invalidate). The old heartbeat token is immediately invalid, because the cloud only accepts the new heartbeatTokenHash.
Headless rotation today
There is currently no dedicated CLI subcommand for "rotate this Device's credentials in place." The supported paths are:
Path A — re-pair (current canonical method)
bash
hivecast logout --local-only --home /tmp/matrix-home
hivecast login --device --cloud https://hivecast.ai --home /tmp/matrix-homeThis produces a fresh Host Link (revoking the previous one), fresh heartbeat token, fresh NATS credentials. The Device's hostId / installId / deviceSlug are preserved (the cloud reuses them). User visible result: the Device "re-connects" with the same name and slug.
Path B — bus-level call (advanced, today)
bash
matrix invoke system.auth auth.hostLink.credentials.refresh \
'{"hostLinkId":"hostlink_<id>","principalId":"p_<sha20>"}'The result includes the new credentials map. Today, applying that map to the Device's local files requires writing the response into the correct credential files at the right paths and modes. There is no helper that does only-rotate-don't-re-pair on the Device side. Target state is a hivecast rotate-credentials subcommand that wraps this. For now: re-pair is the operational path.
Path C — automatic refresh (target state)
host.control could detect heartbeat 401/403 and trigger a re-pair automatically. Today it only emits device.heartbeat.failed and continues trying. Auto-rotation is target state.
When rotation is automatic
The Device's NATS user JWT has a 24h TTL (DEVICE_NATS_CREDENTIAL_TTL_SECONDS = 86_400). On a long-lived linked Device, rotation should happen at least every 24 hours to keep NATS auth working. In practice today: re-pair when NATS auth starts failing.
Why heartbeat token rotation matters separately
The heartbeat bearer token is the credential that proves "I am the Device that owns this Host Link" to the cloud /_auth/host-link/heartbeat endpoint. Rotating it:
- caps the blast radius if the token leaks
- makes "lost laptop" cleanup safer (revoke + new pairing on a new Device produces fresh material; the old material is dead)
- keeps the token forward-secrecy assumption alive
Today's hash-only storage (no plaintext kept cloud-side) means a leaked cloud-side state file does not reveal the token. But rotating still reduces window of validity.
Verification
After re-pairing (Path A):
bash
hivecast link-status --home /tmp/matrix-home
# expect: linked: true, fresh linkedAt, hostConfigLinked: true
# Force a heartbeat
matrix invoke system.devices devices.status '{}'
matrix invoke system.devices devices.list '{"principalId":"p_<sha20>"}'
# expect: source: 'merged', status: 'online'If link-status still shows the old link or the heartbeat fails, the re-pair did not complete cleanly. Check <host-home>/credentials/hivecast-link.json mtime to confirm it was rewritten.
What does NOT change on rotation
hostId(stable Device identity)installId(stable per-install identity)hostName(mutable, but not changed by rotation alone)deviceSlug(stable management projection key)principalId,spaceId,authorityRoot,publicNamespace,routeKey
What changes:
- New
hostLinkId(path A: re-pair) or samehostLinkId(path B: in-place refresh) - New
heartbeatTokenHash - New NATS user JWT + seed
lastRefreshedAt,updatedAt
See also
- Pairing / Pairing grant — same wire shape as rotation.
- Disconnect — distinct from rotation.
- Revoke grant — distinct from rotation.
Source:
projects/matrix-3/packages/system-auth/src/host-auth.ts(HostLinkStore.refresh(),rotateHeartbeatToken(),recordNatsUserPublicKey()); op declarations inprojects/matrix-3/packages/system-auth/src/index.ts:266-271.