Skip to content

Authorization

Authentication tells the platform who a request is from. Authorization tells the platform what they can do. This page documents the present-state model (single check: principal ownership) and the target capability/role model.

Present model: principal ownership

Every Space has an ownerPrincipalId. Every op that touches a Space verifies the calling principal matches.

Examples in code:

ts
// auth.hostLink.create — system-auth/src/index.ts:736
if (!space || space.ownerPrincipalId !== principalId) {
  return { ok: false, error: 'Host link requires a Space owned by the principal' };
}

// auth.space.default — system-auth/src/index.ts:696
if (!space || space.ownerPrincipalId !== principalId) {
  return { ok: false, error: `Space not found for principal: ${nextSpaceId}` };
}

This is the entire authorization model today. A principal:

  • Can read their own data.
  • Can mutate their own data.
  • Can read public data.
  • Cannot read other principals' private data.

There is no:

  • Read-only delegation.
  • Time-bounded grants.
  • Per-op permission ("this principal can read but not revoke").
  • Org-level role.
  • Admin role distinct from "principal that owns the platform Host."

Anonymous and local-client

Two identities are special-cased:

  • Anonymous (authenticated: false): can read /healthz, /api/apps, public namespace claims, and not much else. No NATS bus token.
  • Local-client (localClient: true, principalId: 'host-service'): bypasses every check. Can do anything. This is the local-CLI / supervisor identity.

The local-client identity is the only "operator-like" path today. There is no separate platform-admin role distinct from local-client.

What an admin needs (target)

A platform admin needs to:

  • List all principals.
  • Revoke any Host Link, not just their own.
  • Suspend any Space.
  • Read audit logs across principals.
  • Apply rollouts to fleets.

Today these are reachable only from a local-client connection on the platform Host. There is no "admin: true" flag on a principal record. Target state.

Capability-based vs role-based

The architectural target is capability-based authorization (per ARCHITECTURE-SCOPED-CAPABILITY-RESOLUTION.md in ARCHITECTURE/), not RBAC. The intuition:

  • A capability is a token that says "the bearer may invoke <op> on <resource> for <duration>."
  • Granting capability = minting and delivering a token.
  • Revoking capability = adding the token id to a revocation list.
  • Capabilities compose (you can hold multiple).
  • Capabilities can be delegated (with appropriate parent capability).

The architecture document is mature; the implementation is not.

Why not RBAC

RBAC ("admin," "member," "viewer") is simpler to implement but harder to scope. A capability "read-only access to Space spc_xxx for 24 hours" cannot be expressed cleanly in RBAC without proliferating roles. Matrix's federation model (cross-realm published actors) is naturally capability-shaped.

That said, a small RBAC-flavored layer for the launch is acceptable as long as it doesn't preclude the capability target. The launch admin model could be:

  • principal.role: 'user' | 'platform-admin'.
  • All ops gate on role === 'platform-admin' for cross-principal access.

This is implementable in 200 lines of code and unblocks the platform-admin dashboard. Target state, but tractable.

What enforcement looks like today

  • At the gateway: auth.identity.resolve runs on every HTTP request, attaching identity to the actor invocation.
  • In the actor: each op decides for itself. There is no central authorization middleware. The pattern is if (calling.principalId !== resource.ownerPrincipalId) reject.

Decentralized authorization is correct for an actor system, but it means every op author must remember the check. A test seam (scripts/audit-device-identity-schema.ts and similar) keeps this honest for Device-related ops. A more general "authorization audit" tool is a target.

Cross-Space federation

A principal's actors can publish to system.registry. Other principals can discover and invoke published mounts. This is federation, not authorization — the published actor decides for itself whether to honor a cross-realm invocation. The platform does not enforce a cross-realm authorization layer.

A legitimate target use case: an org Space publishes a chat.support actor. Anyone can invoke chat.support and get rate-limited responses. The actor decides who gets full responses based on its own internal policy.

See also

Source: projects/matrix-3/packages/system-auth/src/index.ts:719-757 is the canonical example of the present-state ownership check. ARCHITECTURE-SCOPED-CAPABILITY-RESOLUTION.md is the design target.