Appearance
Secrets references
A Matrix package's configuration carries tunables — log levels, timeouts, feature flags, limits. It does not carry secrets. Credentials, API tokens, OAuth refresh tokens, and NATS user/passwords go through system.factotum. This page is the boundary spec.
The rule, in one paragraph
Inference credentials NEVER leave the local machine.
system.factotumis the ONE actor that reads/writes credential files. Other actors NEVER touch credentials directly — they request operations through Factotum.
This is rule #4 of the project-root CLAUDE.md. It applies to:
- LLM provider keys (Anthropic, OpenAI, Google, Codex).
- OAuth refresh tokens.
- HiveCast pairing tokens / Device-Link credentials.
- Any third-party API key your actor uses.
- Database passwords.
- Hetzner / cloud-provider tokens used by deployment helpers.
Things that are NOT secrets and CAN go in regular config:
- Public API endpoints (
https://api.openai.com/v1). - Model IDs (
claude-opus-4-7). - Default behaviour flags.
- Numeric limits.
Why credentials never appear in config.defaults
The defaults file ships in the package on disk. Anyone reading the source tree, opening the npm tarball, or browsing the install directory sees it. The OAuth/Codex token-rotation thesis (memory.md, 2026-04-02) was that user-supplied credentials, including OAuth tokens for Anthropic and OpenAI, must never leave the machine they were created on. Putting them in config.defaults.json violates that immediately — and they would also be wrong: defaults are per-package, credentials are per-installation/per-user.
Why credentials never appear in env vars (well, mostly)
Env vars are the most-tempting place to put a secret: OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. They are accessible to every process the user runs, leak through ps, and end up in shell history if someone does OPENAI_API_KEY=sk-... node script.js. The Matrix model is to avoid this entirely:
- Inference providers run inside actors that ask Factotum for the credential by name.
- Factotum reads the credential file (
~/.matrix/credentials/, scoped per provider). - The credential never reaches the package's own runtime memory unless the actor explicitly fetches it for a single call.
The exception: bootstrap-only secrets like MATRIX_NATS_TOKEN for the local NATS connection during installer flows can live in env vars because they are one-shot and scoped to a specific install step. Long-lived secrets do not.
How a package consumes a credential
Through system.factotum. The package config refers to the credential by name, not by value:
json
{
"auth": {
"provider": "anthropic",
"credentialRef": "anthropic-oauth"
}
}The actor looks up the value at runtime:
ts
const cred = await RequestReply.request<{ token: string }>(
this._context!.transport,
'system.factotum',
'factotum.read',
{ name: 'anthropic-oauth' },
);
await callAnthropicApi({ token: cred.token, ... });factotum.read returns the credential value scoped to the requester's principal. The credential never appears in any config file, any env var, any log line, any $introspect response, any persisted state.
Source:
WORKSTREAMS/product-launch/HIVECAST-DEVICE-ENROLLMENT-SPEC.mdfor the OAuth/Device-Link credential lifecycle. The Factotum actor's contract is documented in@open-matrix/system-factotum.
What goes in credentialRef
| Reference | Meaning |
|---|---|
"anthropic-oauth" | The Factotum credential named anthropic-oauth. |
"openai-key" | The Factotum credential named openai-key. |
"github-token" | The Factotum credential named github-token. |
By convention, names are lowercase, hyphen-separated, and namespaced with the provider. The actual storage is in <host-home>/credentials/, written and read only by Factotum.
Pattern: per-user vs per-package credentials
Some credentials are user-supplied (a user's OpenAI API key). Others are package-supplied for shared backend services. Both go through Factotum, but with different scopes:
- Per-user credentials. Stored under the user's principal. Only available to actors running on behalf of that user.
- Per-installation credentials. Stored under the install. Available to system actors (e.g., the local NATS user/pass that gateway and runtimes share).
Packages should not care which scope a credential came from — they ask Factotum, Factotum returns the value scoped to whoever's making the request.
Things you must not do
- Do not log a credential, even at debug level. Log lines end up in logs end up in support tickets.
- Do not put a credential in actor
state. State is observable via$getState. - Do not return a credential from an op handler. It surfaces in callers' logs.
- Do not put a credential in a
setStatecall. Same reason. - Do not pass a credential through a
$promptpayload. It will end up in the agent's transcript.
If you find a credential leaking, treat it as a security incident and rotate the credential immediately.
See also
- Defaults — what defaults can contain.
- Overrides — env / file / service overlays.
- HiveCast Device Enrollment Spec — full Device-Link credential flow.
Source: Root
CLAUDE.md§ "THE RULES" #4 (Credentials go through Factotum ONLY),WORKSTREAMS/product-launch/HIVECAST-DEVICE-ENROLLMENT-SPEC.md(link credentials),@open-matrix/system-factotumactor contract.