Skip to main content
Identity is the platform checkpoint: accounts, keys, and the rule that you only touch what you own. Every other domain assumes a request already passed through it. It exists twice on purpose — the live DB-backed oracle and the pure DB-free reference library — both honouring the same sealed contract (identity.v1): the one-concern-per-module rule applied to trust itself, so authorization logic is testable in full isolation from the database. On every request it resolves three things:
  1. Who is asking — resolved once at the edge, never from the client’s word.
  2. What they touch, and who owns it — every meeting, recording, and workspace has an owner.
  3. Whether they are allowed — a default-deny decision.
It also mints short-lived, single-purpose tokens so an agent acts on your behalf without holding your API key (see zero-trust). Two forms, on purpose:
  • admin-api (core/identity/services/admin-api) — the live, database-backed service: user accounts, API tokens, and the /internal/validate oracle the gateway calls.
  • identity_core (core/identity/src/identity_core) — a pure, dependency-free library of the rules (mint a token, check access, mint a dispatch token, broker a secret), sealed as the identity.v1 contract so every service decides the same way.
This page is the identity domain — accounts, the access model, what’s built, and the roadmap. The cross-cutting trust flow — how a dispatch’s authorization is proven and verified at every hop — is its own document: Identity & trust.

The model

Six rules — the reasoning, the mechanism, and the established practice each comes from.
The agent (the LLM) is outside the trust boundary — it carries proof but never enforces it. Compromise it via prompt injection (OWASP LLM01) and it still cannot exceed its token’s scope; the boundaries enforce, not the model. See Identity & trust.

The request path (grounded)

Everything reaches the domains through the gateway; the terminal is one client. For every call the gateway resolves the key to a user and forwards to the owning domain with X-User-Id stamped on.
The full public surface, grouped by what it protects — this is the real attack surface, and what the security model has to cover (nothing more): Two safety shapes result:
  • Agent space is safe by partition — a workspace, session, or routine is addressed by your id, so no explicit check is needed.
  • Meetings are owned in the meetings domain, so the owner-checked path runs there (GET /transcripts, /ws subscribe authorization).
The leak is where the agent domain reads a meeting directly instead of via the meetings domain — see adoption gaps.

Building blocks

What actually exists today, with the real names. identity_core — the pure rule library, sealed as identity.v1 (core/identity/contracts/identity.v1):
  • Tokens (tokens.py) — ScopedToken(subject, scopes, expires_at); scopes are bot · tx · browser; mint_token / validate_token.
  • Access (access.py) — Resource(kind, id, owner) for kinds meeting_transcript · recording · ws_subscribe; AccessDecision(allow, subject, …, reason); OwnerOnlyPolicy (default-deny, allow iff subject == owner); the front-door function can_access(subject, resource, action).
  • Dispatch tokens (dispatch_tokens.py) — a JWT-style, audience-scoped bearer token signed HS256 in dev (SPIFFE SVIDs in production): DispatchClaims(subject, launcher, workspaces[], tools[], iat, exp); mint_dispatch_token / verify_dispatch_token. subject is who you act for; launcher is what triggered it. may_mount(id, mode) and may_call(tool) are the limits a boundary checks. This is the “single-purpose pass.”
  • Secrets (secrets.py) — the brokered-secrets pattern (HashiCorp Vault): SecretsPort returns a redacted BrokeredSecret (its repr never prints the value) and writes an audit log; the value is fetched on demand, never logged. The real vault (lease, rotation) is deferred — see encryption.
admin-api — accounts and the live oracle:
  • Tables: User and APIToken(token, user_id, scopes[], expires_at, …); tokens look like vxa_<scope>_<random>.
  • POST /internal/validate — the gateway calls this (behind an internal secret, fail-closed) to turn an API key into {user_id, scopes, …}.
  • Three tiers: admin (X-Admin-API-Key, create users/tokens), user (X-API-Key, e.g. set a webhook), internal (X-Internal-Secret, the validate oracle).
Gateway — the edge that resolves the key, stamps X-User-Id, enforces coarse route scopes, and runs the /ws multiplex.

Where we are

Honest status. The design is frozen and the primitives are built — the gap is adoption: the rules exist as a library but are not yet wired into every path that needs them. The constitution names this risk directly: P20 (complete mediation) records that the canAccess seam “was designed but never wired, so it rotted,” and P9 holds that an unenforced rule is only aspirational — a rule that does not turn CI red can be crossed. The front door is real; a few agent-domain endpoints read a meeting’s data directly instead of via the meetings domain, skipping the owner check that already exists in the library. Closing it is wiring, not design: route those reads through the owning domain and decide with OwnerOnlyPolicy.

Where we are going (zero-trust)

The target — a chain of custody where a dispatch’s authorization is proven and verified at every hop (signed tokens, RFC 8693 exchange at the tool boundary, SPIRE/Keycloak via kagenti) — is one document: Identity & trust. It lands as Stage 2 — Trust. The point for this domain is narrow: we run an untrusted, prompt-injectable agent on private data, so safety must come from boundaries that verify, not from trusting the model.

Roadmap — by principle

Each item ties a declared principle to its code reality and the concrete next step, plus the gate or stage that makes it real (P9: an ungated rule is aspirational). Foundations: the agent is untrusted, identity is a chain of custody, self-host & air-gap by default.
subject_of (control_plane/api.py) has a VEXA_AGENT_DEFAULT_SUBJECT fallback for a gateway-less single-user self-host. It is fail-closed (401) when unset; it must stay unset in any multi-tenant deployment, or every caller collapses to one subject.

Data at rest (encryption)

Three stores hold sensitive data at rest (data in transit is encrypted with TLS — the standard that secures HTTPS connections). State and plan: These are tracked in the roadmap status; workspace/bucket encryption is already part of the workspace.v1 contract shape in Stage 0.

Why this much, and not more

Measures are sized to the system: a multi-user product where each person owns meetings and a private workspace, agents run untrusted in isolated containers, all driven from the terminal. The API reduces to “is this yours?”, so the priorities are:
  • owner checks on meetings and workspaces (cross-user exposure is the real risk);
  • credentials out of the agent (the one untrusted, injectable component);
  • encryption of the three at-rest stores.
Heavier machinery — SPIRE everywhere, SSO/SCIM, full air-gap — is staged (Stage 5) for self-host and regulated verticals. The interfaces (can_access, the token shapes) do not change when it lands; only what sits behind them does.