Appearance
Credentials references
The protocol's credentials rule is short:
Source:
CLAUDE.mdRule 4. Quoted: "system.factotumis the ONE actor that reads/writes credential files. Inference credentials NEVER leave the local machine."
Every other actor that needs to use a credential — to call Anthropic, to hit OpenAI, to authenticate with GitHub — holds a reference, never the bytes. Factotum is the only place credential bytes flow.
Why this rule exists
- Locality. Inference credentials (Anthropic OAuth, OpenAI API keys, Codex tokens) belong to the user's local machine. Sending them through actors that might federate or cache means the credentials could leak off-machine. Factotum is the bus's enforced choke point.
- Audit. With one actor handling credentials, every read and write has one place to log, rotate, and revoke. Distributed credential handling is a forensic nightmare.
- Refresh. OAuth tokens expire. With credentials behind one actor, the refresh logic lives in one place. Other actors don't need to know the difference between OAuth and API-key flows.
- Policy enforcement. Factotum is the place where "this credential is for
anthropic.api, not forgithub.api" is checked.
Credential reference shape
Other actors carry references like 'anthropic.access_token' or 'openai.api_key' — a stable name pointing at a credential record Factotum manages. The reference is a string in the actor's config; the bytes are fetched on demand.
The high-level call pattern (target shape):
inference actor: I need to call Anthropic
→ invoke system.factotum, 'credential.get', { provider: 'anthropic' }
← { found: true, type: 'oauth', accessToken: 'sk-ant-oat01-...' }
→ use the token for ONE outbound HTTP call
→ discard the token; do not store, do not echo, do not logThe reply payload IS the credential bytes — but the reply leaves Factotum on the local bus, never crosses federation, and is consumed within the runtime's outbound HTTP request before being garbage collected.
Where credentials live on disk
The Factotum-backed file (verbatim from the architecture-overview doc):
~/.matrix/credentials.json ← Factotum's source of truthJSON shape (projects/matrix-3/packages/docs/content/architecture/...):
json
{
"providers": {
"anthropic": {
"type": "oauth",
"accessToken": "...",
"refreshToken": "...",
"expiresAt": 1714862073221
},
"openai": {
"type": "api-key",
"apiKey": "sk-..."
}
}
}Plus a JetStream KV mirror for in-runtime access. Both are written by Factotum atomically (write .tmp, rename) so no concurrent reader sees a partial file.
Auth boundary table
Source:
CLAUDE.mdRule 6.
| Flow | Where it runs | Why |
|---|---|---|
| Google / GitHub OIDC | HiveCast platform | registered redirect URIs |
| Anthropic / Codex OAuth | local HiveCast install / Factotum | localhost redirect; credentials stay local |
| Domain verification | HiveCast platform | DNS TXT check |
| Inference API calls | local runtime through Factotum-owned credentials | tokens are local |
The platform handles identity (who the principal is); local Factotum handles service credentials (what the principal can call). The boundary is rigid. Inference tokens never reach the platform.
What other actors MUST NOT do
- Read
~/.matrix/credentials.jsondirectly. (Path traversal violation- bypass of Factotum's policy layer.)
- Cache credential bytes after using them. (One-shot consumption.)
- Forward credentials to other actors. (Defeats locality.)
- Log or emit credential bytes in
$events. (Audit / leakage.) - Accept credentials in a payload from another actor. (Same.)
Factotum imports
Factotum supports importing from existing CLI credential stores (~/.claude/.credentials.json, ~/.codex/auth.json):
op: credential.import-detected
reply: { detected: [{ source: 'claude-cli', provider: 'anthropic', hasToken: true }] }
op: credential.import
payload: { source: 'claude-cli', provider: 'anthropic' }
reply: { ok: true }The import path reads the source file once, copies relevant fields into Factotum's record, and proceeds. The original file is left in place.
Key validation
Before storing a credential, Factotum validates it where possible:
op: provider.test-key
payload: { provider: 'anthropic', apiKey: 'sk-ant-api03-...' }
reply: { valid: true }This catches typos and revoked keys before persisting. The validation call uses a tiny outbound API call — not a full inference round-trip.
Token refresh
OAuth-typed credentials carry refreshToken + expiresAt. Factotum runs a background timer (every 5 min) to refresh tokens before expiry. The refresh exchange is local-only. Other actors don't see the refresh; they just keep getting valid tokens from credential.get.
What is NOT a credential
The session cookie / bearer JWT for the platform identity (mx_session) is NOT a credential in the Factotum sense. It is a transport-level authentication token issued by system.auth, validated by auth.session.validate, and routed by HTTP cookie semantics. It does not pass through Factotum.
The Host Link heartbeat token IS a Factotum-style credential conceptually (bearer for an outbound call), but it is owned by system.auth and the gateway, not by Factotum, because it's about identity, not service credentials. See Device grants.
See also
- Principal identity — identity vs credentials.
- Capability tokens — capabilities vs credentials.
- Device grants — Host Link heartbeat token.
CLAUDE.md— Rules 4 and 6.