Appearance
Capability model
A capability token is a credential that says "the bearer is authorized to do X in scope Y." This is distinct from an identity token, which says "the bearer is principal Z." Identity is verified once; capability is enforced on every operation.
The substrate has two implementations of capability discipline today, in different states of maturity:
- The C# bridges (
com-matrix-bridge,win32-matrix-bridge, both landed on master at commit937ad7d5) implement a full capability realm: mint, attenuate (Mark Miller's "capabilities can only be attenuated, never amplified"), revoke, and audit. This is the substrate's reference implementation. - The TypeScript side (
@open-matrix/system-auth) implements a partial model today: status-gated principal capabilities and Host Linkscopes[]recorded at pairing time. Per-op enforcement and attenuable per-op tokens are target state.
This page explains the conceptual model, what is enforced today on each side, and what target state adds on the TypeScript side. The on-the-wire shapes live in Authorization / Capability tokens.
The C# bridge realm (reference implementation)
BridgeSecurityRealm.cs is a working capability realm. Every authorized COM and Win32 invocation goes through it. The same code shape is the target for the substrate-side TypeScript expansion.
What the realm does:
- Mint a capability token for a target (e.g. a ProgID, or
*for wildcard) with a set of rights, a TTL, and optional caveats. Mint requires either an admin secret or an existing admin capability — minting is itself a privileged operation. - Attenuate an existing capability — produce a child token with strictly fewer rights, sooner expiry, and additional caveats. The attenuation operation is monotonic: rights can be removed, never added; expiry can shrink, never grow.
- Verify a capability before each call: not revoked, not expired, target matches, required right is held.
- Revoke a capability by id. Revocation is checked on every verify, so it is effective immediately.
- Audit — every mint, attenuate, verify (success and denial), and revoke produces a structured audit event with
principal,action,target,sourceRealm,targetRealm,outcome, and (on denial) adenialReason.
This is the substrate's load-bearing capability mental model. Read the C# code if you want to see how the pieces compose; the planned TypeScript expansion follows the same shape.
TypeScript side: today
The on-the-wire capability story for the bus is intentionally minimal at this point. Three gates do most of the work today, and they cover the cross-tenant threat:
Principal capabilities (status-gated)
HostPrincipalStore.hasCapability() and listGrants():
ts
// projects/matrix-3/packages/system-auth/src/host-auth.ts:421-429
hasCapability(principalId: string, _scope: string, _capability: string): boolean {
return this.getById(principalId)?.status === 'active';
}
listGrants(principalId: string): Array<{ capability: string }> {
return this.getById(principalId)?.status === 'active'
? ['identity', 'bus', 'prompt', 'install', 'invoke'].map((capability) => ({ capability }))
: [];
}The five capability names used today:
| Capability | Meaning (today) |
|---|---|
identity | resolve as a principal in browser shells |
bus | open a bus token / connect to NATS |
prompt | send $prompt envelopes to actors |
install | install packages |
invoke | invoke actor ops |
Today this is not a per-op authorization gate. It is metadata that says "an active principal has these capabilities." A suspended principal has none. Beyond status, individual ops do not currently check hasCapability(principalId, scope, capability) before proceeding.
Host Link scopes
IHostLinkRecord.scopes: readonly string[] is a sorted, de-duplicated list of capability names. HostLinkStore.create() accepts scopes input, normalizes via normalizeScopes(), and stores them on the record. The scopes field is recorded at pairing time. Today it is consulted by audit reads but is not a per-op gate either.
Policy preset op gating
The substrate's policy engine (PolicyPresets.ts) is the live op-gate for the bus today. strict denies by default and explicitly allows the framework's SYSTEM_OPS plus a small allowlist of agent/chat/component-lifecycle ops. standard allows by default and explicitly denies factory.create-from-code. This is op-name gating, not identity-and-capability gating, but it is the actually-running per-op enforcement seam.
What $source-code and $prompt are
$prompt, $source-code, and other $<facet> ops belong to the Matrix actor model's introspection / control surface. They are gated by the actor framework itself, plus by the policy engine. Today an actor accepts $prompt unless it overrides the framework's default; capability filtering at the framework level is target state.
Target state — substrate-side capability tokens
The target capability story has three additions on top of today's status-gate plus policy preset:
Per-op capability check
Actor ops get an explicit "requires capability X in scope Y" annotation on the actor schema. MatrixActor.dispatch (or a middleware layer) consults auth.principal.hasCapability and rejects the op envelope before the actor sees it.
Capability-bound bus tokens
Today's BUS_TOKEN_DURATION_MS = 5 min bus token (in HostSessionService.createBusToken()) carries purpose: 'bus' and the principal's sub. A target-state addition would attach a capability list directly to the token's claims (e.g. caps: ['bus', 'invoke']), allowing the bus side to verify capabilities from the token alone without round-tripping to the principal store.
Attenuable, revocable capability tokens (the bridge model, lifted)
The C# bridge realm's mint/attenuate/revoke/verify/audit cycle becomes the substrate-side default. A parent token with broad caps can mint a child with a strict subset; the child holder cannot exceed the parent's caps; revocation cuts the child off immediately; every step writes an audit event.
Capability scopes (target state)
Today the _scope argument to hasCapability() is unused. A target-state scope vocabulary:
| Scope | Meaning |
|---|---|
* | all scopes |
space.<spacePath> or space.<spaceId> | within one Space |
host.<hostLinkId> | within one Device |
mount.<mount> | within one specific actor mount |
Combined with capability names, this gives:
hasCapability(principalId, 'space.alt.stories', 'invoke')
hasCapability(principalId, 'host.hostlink_abc', 'install')
hasCapability(principalId, 'mount.system.inference.openai', 'prompt')Status: target state. Per-op capability gates and the wire format for substrate-side capability tokens are not yet implemented. The C# bridge realm is the working precedent. Two open design choices remain — the wire shape (macaroons vs bearer JWT with
capsclaims vs NATS-account-permissioning extension) and the offline-verification story — and they need to land before code does.
Host Link scopes vs principal capabilities
It is worth noting these two scope vocabularies should not be conflated:
- Principal capabilities (
hasCapability,listGrants) describe what the principal can do. - Host Link
scopes[]describes what this Device was granted when it paired.
A Device cannot exceed its Host Link scopes, even if its principal has broader principal-level capabilities. The pairing flow has the opportunity to attenuate.
See also
- Authorization / Capability tokens — the wire shape and what code paths reference them today.
- Authorization / Topic claims — the scoping vocabulary.
- Authorization / Actor permissions — what actors gate today.
- Security model — the policy presets that govern operating posture.
Source:
BridgeSecurityRealm.cs(the reference capability realm — mint, attenuate, revoke, audit);projects/matrix-3/packages/system-auth/src/host-auth.ts(HostPrincipalStore.hasCapability()at lines 421-429,HostPrincipalStore.listGrants()at lines 425-429,HostLinkStore.create()acceptingscopesat line 851;normalizeScopes()at lines 1775-1784);PolicyPresets.ts(op-gating policy engine).