Appearance
Security logs
This page enumerates the actual log surfaces today: what is recorded, where, in what format, and how to read it. The fragmented today informs what unified auditing should look like (target state).
Today's three log surfaces
1. Bus events
Actors emit events on the bus (emit(eventName, payload)). Subscribers that care can record them. There is no central audit subscriber today; each consumer subscribes to what it needs.
Security-relevant emitters:
| Actor | Event | When |
|---|---|---|
system.devices | devices.added { deviceId } | First registration |
system.devices | devices.offline { deviceId, reason } | Heartbeat timeout or explicit deregister |
host.control | device.heartbeat { deviceId, runtimeCount, registryRoot? } | Each successful heartbeat |
host.control | device.heartbeat.failed { deviceId?, reason } | Each failed heartbeat |
system.factotum | factotum.credential-stored { provider } | Credential added or replaced |
system.factotum | factotum.credential-deleted { provider } | Credential removed |
system.factotum | factotum.credential-refreshed { provider, newExpiresAt } | OAuth refresh succeeded |
system.factotum | factotum.credential-expired { provider } | OAuth refresh failed |
system.factotum | factotum.needkey { provider, requestedBy } | Lease attempted with no credential present |
These events are observable but not persisted by default. A target-state audit subscriber would record them with timestamps and durable storage.
2. Per-runtime log files
<host-home>/logs/runtimes/<runtime-id>/<stream>.log per runtime. These are stdout/stderr from the runtime process. Security-relevant entries appear here for runtimes that include actorLog() calls:
system-gateway-httplog lines for HTTP auth events (success / fail, body-token mismatch, revoked Host Link rejected)system-authlog lines for principal lookup, OIDC verify, session revoke
Format is per-runtime; there is no schema. Search with journalctl (on systemd-managed Hosts) or grep on the file directly.
3. State-store mutations
The cloud system.auth state file (HostAuthStateStore) is itself a log of sorts: every mutation produces a new save. Inspecting timestamps on the records (createdAt, updatedAt, revokedAt, lastRefreshedAt) gives a partial history.
Specifically:
IHostLinkRecordmutations:createdAt,updatedAt,lastRefreshedAt,revokedAt.IHostPublicNamespaceClaimmutations:createdAt,updatedAt.IHostPrincipalRecordmutations:createdAt,updatedAt.revokedSessions[]entries:{ jti, principalId, expiresAt }.
This is "the state file is the log." It's incomplete because intermediate values are overwritten, but it captures the most recent mutation per record.
What is NOT logged today
- Per-op invocation traces. There is no record of "principal X invoked op Y at time T."
- Failed
auth.identity.resolvecalls (e.g. invalid cookie). The cookie is rejected and the op returns; no record. - NATS subject permission denials. NATS itself logs them server-side (depending on its log level), but Matrix-side actors do not see them.
credential.materializecalls. Only thefactotum.credential-storedevent is emitted on store; lease/materialize is not logged.auth.namespace.claimrejection reasons. The op returns{ ok: false, error: '...' }and the call site sees the error; no audit log entry.
A unified audit log would close these gaps. Implementation cost is the schema design; storage cost is per-event size × event rate.
Target state — unified audit log
A system.audit actor (target state) would:
- Subscribe to all security-relevant bus events.
- Record
auth.identity.resolveoutcomes via a subscribe-and-record pattern (the actor adds itself as a subscriber). - Persist records to a JetStream KV or stream with operator-bounded retention (e.g. 90 days).
- Expose
audit.searchfor operators to find specific events. - Cross-reference principalId, hostLinkId, deviceId so investigators can pivot.
Design choices to commit:
- Append-only vs garbage-collected. Append-only is simpler but unbounded.
- Per-principal vs per-tenant. If per-principal, GDPR-style erasure is cleaner.
- On-Device vs cloud. Device-side audit is private but lost on unlink; cloud-side is centralized but introduces another cross- boundary surface.
Operator runbooks against current logs
"Did a Device successfully pair?"
bash
matrix invoke system.auth auth.hostLink.list \
'{"principalId":"p_<sha20>"}'
# Look for the most recent record with status: 'active'.
# createdAt is the pairing timestamp."Did this Device's heartbeat fail recently?"
Subscribe to host.control.device.heartbeat.failed events on the bus. There is no historical log; you only see going forward.
bash
# bus subscribe (using whatever bus client you have)
nats sub "<authority-root>.host.control.$events""Were any credentials added or removed today?"
Subscribe to system.factotum.$events. Same constraint: forward-only.
"Did anyone access this principal's data without authentication?"
Today: not directly answerable. Inspect HTTP gateway log files for the specific Host:
bash
sudo journalctl -u hivecast-host.service --since "today" \
| grep -E '(401|403|auth)'See also
- Device events — bus events specific to Devices.
- Package events — bus events specific to packages.
- Admin actions — what should be audited and where it shows up.
- Reference / Token schema — the
jticlaim that revoked-session entries reference.
Source: event emitters identified in
projects/matrix-3/packages/host-control/src/HostControlActor.ts,projects/matrix-3/packages/system/src/SystemDevicesActor.ts,projects/matrix-3/packages/system-factotum/dist/index.js. State-file mutations inprojects/matrix-3/packages/system-auth/src/host-auth.ts.