Skip to content

Secret rotation

Each credential layer has its own rotation cadence and runbook. This page lists them in order of severity (highest blast radius first).

Layer 1 — HiveCast session secret (auth.sessionSecret)

Blast radius: every session JWT signed by this secret.

Rotation:

  1. Generate a new secret: node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))' or HostSessionService.generateSecret().
  2. Set the new value in the system.auth actor's config.
  3. Restart system.auth (or perform a config reload if supported).
  4. All existing JWTs become invalid. Users will be redirected to login on their next request.

Trigger:

  • Suspected compromise of the secret (e.g. accidentally committed to git).
  • Periodic rotation (e.g. quarterly).
  • Personnel change with access to the secret.

Cost: all active sessions are invalidated. Users sign in again.

Layer 2 — Principal NATS account seed

Blast radius: every per-Device user JWT issued under this account.

Rotation:

  1. Mint a new account seed via @open-matrix/nats-auth helpers.
  2. Re-issue the principal's account JWT with the new seed.
  3. Update the operator/JWT NATS configuration.
  4. Re-issue every Device user JWT under the rotated account (each Device must re-pair or run auth.hostLink.credentials.refresh).

Trigger:

  • Compromise of the account seed.
  • Operator rotation policy.

Cost: every linked Device for that principal must re-pair before its NATS user JWT works against the rotated account. Plan maintenance window accordingly.

Layer 3 — Device-scoped NATS user JWT

Blast radius: one Device.

Rotation:

bash
matrix invoke system.auth auth.hostLink.credentials.refresh \
  '{"hostLinkId":"hostlink_<id>","principalId":"p_<sha20>"}'

This mints a fresh user JWT (24h TTL by DEVICE_NATS_CREDENTIAL_TTL_SECONDS) plus a fresh heartbeat token. The Device's local <host-home>/credentials/hivecast-nats.json and <host-home>/credentials/hivecast-host-link-token are rewritten.

The headless / canonical path is to re-pair: hivecast logout --local-only then hivecast login --device. Both produce the same artefact set.

Trigger:

  • Routine — every 24 hours (target state: automatic; today: re-pair when needed).
  • Suspected token leak.
  • Device handoff (e.g. employee reassignment of a corporate VM).

Cost: none for end users; brief NATS reconnect window for the Device.

Layer 4 — Heartbeat bearer token

Blast radius: one Host Link's ability to post heartbeats.

Rotation: rotates with Layer 3. The same auth.hostLink.credentials.refresh op mints a fresh heartbeat token along with the NATS creds.

The cloud stores only the SHA-256 hash; the plaintext is returned exactly once and written to <host-home>/credentials/hivecast-host-link-token (mode 0o600).

Trigger:

  • Routine — bound to Layer 3.
  • Suspected leak (token in logs, leaked through a debug surface, etc.).

Layer 5 — Local provider OAuth (Anthropic / Codex)

Blast radius: one Device's ability to call one provider's API.

Rotation:

For OAuth providers with refresh tokens:

bash
matrix invoke system.factotum credential.refresh \
  '{"provider":"anthropic"}'

(Today's credential.refresh is partial — it returns the existing token's expiry. Full refresh-token round-trip is target state. The canonical operational path today is re-running the OAuth flow when a token expires.)

For API-key providers:

  1. Generate a new key in the provider's console.
  2. matrix invoke system.factotum credential.store {...} with the new key.
  3. Optionally credential.validate first.
  4. credential.delete the old key (or just store the new one — store replaces).
  5. Revoke the old key in the provider's console.

Trigger:

  • Provider's normal rotation policy (e.g. annual).
  • Suspected leak.
  • Periodic policy.

Cross-rotation dependencies

RotatingForces rotation of
Layer 1(none — sessions just invalidate)
Layer 2Layer 3 + Layer 4 for every linked Device
Layer 3Layer 4 (atomic with the same op)
Layer 4Layer 3 (atomic with the same op)
Layer 5(none — independent)

Verification after rotation

After rotating any layer, verify:

bash
# Layer 1: a fresh login still works:
curl -fsS https://hivecast.ai/api/auth/login/google
# expect: 302 redirect to Google

# Layer 2: per-Device NATS still resolves to the principal:
matrix invoke system.auth auth.principal.authorityRoot \
  '{"principalId":"p_<sha20>"}'
# expect: { ok: true, root: '...' }

# Layer 3 / 4: the Device heartbeats successfully:
matrix invoke system.devices devices.list \
  '{"principalId":"p_<sha20>"}'
# expect: source: 'merged', status: 'online'

# Layer 5: provider call works:
matrix invoke system.factotum credential.validate \
  '{"provider":"anthropic","type":"oauth","accessToken":"<paste>"}'
# expect: { ok: true, model: '...' }

Audit trail

Today, rotation produces:

  • Layer 1: a system.auth config-change record (operator-side; not tracked by system.auth itself).
  • Layer 2: a write to HostCredentialStore credentials[] table.
  • Layer 3 / 4: lastRefreshedAt updated on the IHostLinkRecord.
  • Layer 5: factotum.credential-stored event emitted.

Target state would tag each rotation with an explicit audit log entry including actor, principalId, reason, timestamp. See Auditing / Security logs.

See also

Source: projects/matrix-3/packages/system-auth/src/host-auth.ts (HostSessionService.generateSecret, HostLinkStore.rotateHeartbeatToken, auth.hostLink.credentials.refresh); projects/matrix-3/packages/system-factotum/dist/index.js (credential.refresh, credential.store).