🔐

AUTH

P0 · Foundation Planned · P0 design phase Owner: CSO (vacant) → interim CEO

The shared identity plane — every human, agent, and service authenticates here, and every cross-module call has its RBAC + Scope Contract Grant predicate evaluated here.

AUTH is the most sensitive single piece of CyberOS infrastructure. It owns the identity database, signs the JWTs every other module trusts, and applies the same predicate to a human and to an agent so the platform never has a "second-class" auth path. The shape is conventional: a Postgres identity store, an OAuth 2.1 authorisation server (PKCE-only, audience-bound), an OIDC SSO front door, WebAuthn passkeys for elevated roles, TOTP for everyone else, and an RBAC engine that consumes Scope Contract Grants from BRAIN. What is unconventional is the agent-equal property: a persona-versioned agent JWT is evaluated by the same code path as a human one, so "the AI did it" is never a special case — it is just another actor with a smaller scope. PRD §8.6 locks the role catalogue, §9.6 specifies the FRs, §11.2.3 sets the security NFRs. This page documents the planned implementation at cyberos/services/auth/.

AUTH is the identity and authorisation service for every actor inside CyberOS — humans (CEO, CFO, members, clients) and agents (CUO persona-versioned tokens, Skill bot accounts, scheduled tasks). It speaks OAuth 2.1 + PKCE to the world, OIDC for SSO federation, WebAuthn L3 for passkey enrolment, and TOTP / RFC 6238 for the soft-MFA fallback. Inside, it sits on PostgreSQL with row-level security keyed by tenant_id, a Redis hot-path session cache, and an HSM-resident RS256 signing key. Every authentication and authorisation decision lands as a chained audit row in BRAIN — so "did this agent really have permission to delete that record?" is never an interpretation, it is a replay.

Status
Planned
P0 · design phase · M+1
Est. LoC
~7,000
Rust (axum) + sqlx
Planned tests
120+
incl. WebAuthn / OAuth flow suite
RBAC roles
22
PRD §8.6.1 closed catalogue
JWT
RS256 · 15 min
refresh: 30 d rotating
MFA
TOTP + WebAuthn
passkey mandatory at T2+
Depends on
BRAIN · OBS
audit + telemetry sink
Used by
All 22 modules
cross-module call → predicate
1

Why AUTH exists

Per-module auth is one of the easiest mistakes to make and one of the hardest to unmake. The first version of any platform ships with one module that has "owns its own login", the second module copies the pattern, the third inherits a subtle bug, and within a year there are four MFA implementations, three session models, and zero coherent answer to "who can do what". CyberOS treats this as a P0 design constraint: a single authentication service, a single authorisation predicate, a single audit trail. Every module that wants to know "can this actor do X to that resource?" asks AUTH the same way.

🆔
One identity, many shapes

Humans, agents, service accounts, and API keys all materialise as Subject rows. Same predicate; different scopes.

⚖️
Agent-equal evaluation

An agent JWT is not a magic backdoor. The RBAC engine evaluates it with exactly the same code path as a human JWT — just with a smaller, persona-bound scope.

📜
Every decision auditable

Login, logout, MFA challenge, role grant, scope decision — each one writes a chained row to BRAIN. "Did Compliance permit this?" → grep auth.decision.

The bet is the same bet BRAIN makes about audit: pay the cost once at the infrastructure layer, and every module inherits the property for free. Without AUTH as a shared plane, every module re-implements login, every module ships its own MFA bug, and "who has access to what" is a vibes question. With AUTH as a shared plane, the answer is a row in auth.decision and a JWT claim — the regulator can read it, the auditor can verify it, the engineer can replay it.

2

What it does — 5W1H2C5M

A structured decomposition of AUTH's scope. Every cell traces back to PRD §8.6 + §9.6 + §11.2.3.

AxisQuestionAnswer
5W · WhatWhat is AUTH?A Postgres-backed OAuth 2.1 + OIDC server with WebAuthn / TOTP MFA, an RBAC engine, a Scope Contract Grant evaluator, and an HSM-resident JWT signing key. Single Rust binary, exposed over gRPC internally and HTTPS externally.
5W · WhoWho authenticates?Humans: founders, members, clients, employees, contractors. Agents: CUO persona-stamped tokens, Skill bot accounts, scheduled tasks. Services: module-to-module mTLS clients (separate cert root). Owner: CSO seat (interim CEO until filled).
5W · WhenWhen does auth happen?(a) at session start (login + MFA); (b) at every cross-module API call (RBAC predicate); (c) at every MCP tool invocation (Scope Contract Grant); (d) on token refresh (rotation + reuse detection). Hot-path RBAC is sub-millisecond against Redis cache.
5W · WhereWhere does it run?P0: single region (Singapore SG-1) backed by AWS RDS Postgres + ElastiCache Redis. P3: multi-region active-active with eventual JWT consistency. WebAuthn / TOTP secrets at rest in KMS-wrapped column-level encryption.
5W · WhyWhy a separate layer?Because per-module auth is the single biggest source of platform-wide CVE class regression. Centralise the primitive, never the policy.
1H · HowHow does it work?OAuth 2.1 with PKCE for browser flows; client_credentials for service-to-service; refresh_token with rotation + reuse detection for long sessions; WebAuthn discoverable credentials for passkey login; TOTP RFC 6238 for fallback; OIDC for SSO federation with Google / Microsoft 365 / Okta. RBAC is a single Rego-like predicate evaluated at every cross-module call. Each decision is a chained audit row.
2C · CostCost budget?P0: ~$60/month (RDS t4g.small + ElastiCache cache.t4g.micro + one Fargate task). 50-tenant: ~$220/month. Per-login cost ~$0.000002 amortised; RBAC predicate ~$0.00000005.
2C · ConstraintsConstraints?(a) FIDO Alliance L3 WebAuthn for elevated roles. (b) Vietnamese Decree 53: data-residency proof for VN tenants. (c) Vietnam Decree 53 Art. 26 — incident notification ≤ 24h to MoPS. (d) PDPL Art. 14 — DSAR data export. (e) Per-module auth is strictly forbidden.
5M · MaterialsStack?Rust 1.81 · axum 0.7 · sqlx · PostgreSQL 16 · Redis 7 · WebAuthn-rs · totp-rs · rsa for JWT signing · AWS KMS for signing-key wrap · OpenTelemetry SDK · LiteFS for read replicas at P3+.
5M · MethodsMethod choices?OAuth 2.1 (not 2.0 — PKCE mandatory). RBAC + Scope Contract Grants (no ABAC complexity at P0). JWT RS256 (not HS256 — separates signer from verifier). Argon2id for password hashing. Refresh-token rotation with reuse detection (RFC 6749 §10.4). Row-level security on every identity table keyed by tenant_id.
5M · MachinesDeployment?Fargate task in SG-1 (P0). Multi-AZ Postgres RDS. Redis cluster mode at P3+. WebAuthn relying-party ID = the company root domain (P0: cyberos.com).
5M · ManpowerWho maintains?0.5 FTE today (covered by CEO). By P1: CSO seat owns 100% capacity + 24/7 on-call rotation with CTO.
5M · MeasurementHow measured?N(FR pending)..012 (PRD §11.2.3) — zero tenant-data leakage, zero compensation in BRAIN, mandatory mTLS, OWASP Gen AI Top-10 mitigations all green. Per-tenant auth-decision dashboard. Annual penetration test (P2+).
3

Architecture

AUTH is one Rust service with five surfaces (OIDC issuer, OAuth 2.1 token endpoint, WebAuthn endpoints, RBAC gRPC API, admin REST), three stores (Postgres for identity / role / session, Redis for hot-path session and RBAC cache, KMS for signing-key wrap), and a single audit sink (BRAIN). The diagram below shows the canonical request flow for a cross-module call.

graph TB subgraph CLIENTS ["Clients"] BROWSER["Browser SPA
(OIDC + PKCE)"] MOBILE["Mobile (P3)"] AGENT["CUO agent
persona-stamped JWT"] SVC["Module service
(mTLS · client_credentials)"] end subgraph EDGE ["Edge"] AR["Apollo Router
verifies JWT @JwtAuth"] MCP["MCP Gateway
verifies OAuth 2.1 PRM"] end subgraph AUTH ["AUTH service (Rust · axum)"] OIDC["OIDC issuer
/.well-known/openid-configuration"] OAUTH["OAuth 2.1 token endpoint
PKCE · audience-bound"] WA["WebAuthn / TOTP
passkey + 2FA"] RBAC["RBAC engine
predicate evaluator"] SCOPE["Scope Contract
Grant resolver"] ADMIN["Admin REST
role / tenant CRUD"] end subgraph STORES ["Stores"] PG[("PostgreSQL
identity · roles · sessions
RLS by tenant_id")] RED[("Redis 7
session + RBAC cache
TTL ≤ 15 min")] KMS[("AWS KMS
RS256 signing key
wrapped on disk")] end subgraph SINKS ["Audit & telemetry"] BRAIN["🧠 BRAIN
auth.decision rows"] OBS["👁 OBS
traces + metrics"] end BROWSER --> OIDC MOBILE --> OIDC OIDC --> OAUTH AGENT --> OAUTH SVC --> OAUTH OAUTH --> WA WA --> PG OAUTH --> PG OAUTH --> KMS AR --> RBAC MCP --> RBAC RBAC --> SCOPE SCOPE --> PG RBAC --> RED ADMIN --> PG OAUTH --> BRAIN RBAC --> BRAIN AUTH --> OBS classDef planned fill:#cba88a,stroke:#4338ca classDef store fill:#f5f3ff,stroke:#7c3aed classDef sink fill:#f5ede6,stroke:#45210e class OIDC,OAUTH,WA,RBAC,SCOPE,ADMIN,AR,MCP planned class PG,RED,KMS store class BRAIN,OBS sink

Internal components

ComponentPath (planned)Responsibility
oidc.rsservices/auth/src/oidc.rsOIDC issuer — discovery doc, JWKS endpoint, ID-token issuance. Pure OpenID Connect Core 1.0 compliance.
oauth.rsservices/auth/src/oauth.rsOAuth 2.1 token endpoint — PKCE-required, audience-bound, refresh-token rotation, reuse detection.
webauthn.rsservices/auth/src/webauthn.rsWebAuthn L3 — credential creation, assertion verification, attestation parsing. Wraps webauthn-rs.
totp.rsservices/auth/src/totp.rsRFC 6238 TOTP — enrollment QR generation, code verification, replay window enforcement.
rbac.rsservices/auth/src/rbac.rsRBAC predicate evaluator. Input: (subject, action, resource). Output: allow / deny + audit record. Hot-path cache in Redis.
scope.rsservices/auth/src/scope.rsScope Contract Grant resolver. Reads agent-persona scope sheets from BRAIN; emits effective scope set per JWT.
session.rsservices/auth/src/session.rsSession manager — issue, validate, revoke. Device tracking + impossible-travel detection.
jwt.rsservices/auth/src/jwt.rsJWT issuance / verification. RS256 via KMS; key rotation; JWKS publication.
impossible_travel.rsservices/auth/src/impossible_travel.rsGeographic-velocity check on consecutive logins (PRD (FR pending)); challenge if velocity > 1,000 km/h.
password.rsservices/auth/src/password.rsArgon2id hashing, password-policy enforcement, breach-list check via HIBP k-anonymity API.
device.rsservices/auth/src/device.rsDevice fingerprinting ((FR pending)), new-device email, force-logout endpoint.
magic_link.rsservices/auth/src/magic_link.rsMagic-link onboarding flow ((FR pending)) — single-use, time-bound, onboarding-only.
audit_bridge.rsservices/auth/src/audit_bridge.rsWrites every decision to BRAIN via the canonical writer. Includes actor, action, resource, decision, reason.
admin.rsservices/auth/src/admin.rsAdmin REST — create tenant, assign role, revoke session, view audit. CSO + CEO scope only.
migrations/services/auth/migrations/sqlx migrations. Every table has RLS by tenant_id. Indices for hot-path queries.
4

Data model

Identity, role, and session live in PostgreSQL with row-level security keyed by tenant_id. The schema is normalised but optimised for hot-path RBAC: the subject_role and role_permission tables are de-normalised into a materialised subject_effective_permission view that Redis caches with a 15-minute TTL.

erDiagram TENANT ||--o{ SUBJECT : "owns" TENANT ||--o{ ROLE : "defines" SUBJECT ||--o{ SESSION : "creates" SUBJECT ||--o{ API_KEY : "owns" SUBJECT ||--o{ WEBAUTHN_CREDENTIAL : "registers" SUBJECT ||--o| TOTP_SECRET : "has" SUBJECT ||--o{ SUBJECT_ROLE : "holds" ROLE ||--o{ SUBJECT_ROLE : "granted" ROLE ||--o{ ROLE_PERMISSION : "carries" PERMISSION ||--o{ ROLE_PERMISSION : "grants" SUBJECT ||--o{ SCOPE_CONTRACT_GRANT : "has agent scope" SUBJECT ||--o{ AUTH_DECISION : "subject of" SUBJECT ||--o{ DEVICE : "trusts" SESSION }o--|| DEVICE : "issued from" SERVICE_ACCOUNT ||--|| SUBJECT : "is a" TENANT { uuid id PK string slug string display_name string country "VN or SG or other" string data_residency "vn-hanoi or sg-1" timestamp created_at } SUBJECT { uuid id PK uuid tenant_id FK string kind "human or agent or service or api_key" string email string display_name string status "active or locked or disabled" string password_hash "argon2id - null for agents" timestamp created_at timestamp last_login_at } ROLE { uuid id PK uuid tenant_id FK string code "CEO or CFO or CHRO or CTO or other" string display_name string scope_level "platform or tenant or project" } PERMISSION { string code PK "rew.write_run or brain.put or other" string action "read or write or delete or invoke" string resource_class } ROLE_PERMISSION { uuid role_id FK string permission_code FK } SUBJECT_ROLE { uuid subject_id FK uuid role_id FK timestamp granted_at timestamp expires_at uuid granted_by FK } SESSION { uuid id PK uuid subject_id FK string jti "JWT ID" string refresh_jti timestamp issued_at timestamp expires_at string ip_address string user_agent uuid device_id FK string status "active or revoked" } API_KEY { uuid id PK uuid subject_id FK string prefix "ck_live_…" string secret_hash "argon2id" string scopes "comma-separated" timestamp last_used_at timestamp expires_at } WEBAUTHN_CREDENTIAL { bytea credential_id PK uuid subject_id FK bytea public_key bigint sign_count string aaguid string transports timestamp created_at } TOTP_SECRET { uuid subject_id PK bytea secret_encrypted "KMS-wrapped" timestamp enrolled_at } DEVICE { uuid id PK uuid subject_id FK string fingerprint_sha256 string label timestamp first_seen_at timestamp last_seen_at bool trusted } SCOPE_CONTRACT_GRANT { uuid id PK uuid subject_id FK "agent persona" string persona_version "cuo-v2.3.1" string scope_set "comma-separated permission codes" timestamp valid_from timestamp valid_to } SERVICE_ACCOUNT { uuid subject_id PK string client_id bytea client_secret_hash string allowed_audiences } AUTH_DECISION { uuid id PK uuid tenant_id FK uuid subject_id FK string action string resource string decision "allow or deny or challenge" string reason_code timestamp ts string brain_chain "linked audit row chain hash" }

RBAC role catalogue (PRD §8.6.1 — closed)

Role codeDisplayScope levelExamples of permissions
founderFounder / CEOplatformAll; sign authority.
cfoChief Financial Officertenantfinance.*, rew.read_run (not write).
chroChief Human Resources Officertenanthr.*, rew.write_run (with CFO co-sign).
ctoChief Technology Officertenantengineering.*, auth.admin.
csoChief Security Officertenantauth.*, obs.audit_read, key-rotation.
cloChief Legal Officertenantlegal.*, brain.delete_purge_approve.
dpoData Protection Officertenantbrain.dsar_export, auth.audit_read.
cdoChief Data Officertenantbrain.admin, obs.read.
cpoChief Product Officertenantproduct.*, kb.write.
cmoChief Marketing Officertenantmarketing.*, crm.read.
ccoChief Customer Officertenantsupport.*, crm.write.
memberOperating MembertenantScope-narrowed per module; default chat.* + brain.read_own.
contributorExternal ContributorprojectProject-scoped read + comment only.
clientClient UserprojectPer-project shared workspace; no cross-tenant access.
serviceService Accounttenantclient_credentials only; explicit audience.
agent.cuoCUO AgenttenantRouting only; never destructive without human gate.
agent.skillSkill AgenttenantPer-skill scope; capability-broker gated.
agent.scheduledScheduled TasktenantSingle-purpose; cron-defined scope.
+ 4 more(auditor, intern, vendor, partner)tenant / projectSee PRD §8.6.1.
5

API surface

Four surfaces: an OIDC + OAuth 2.1 HTTPS edge for browsers and agents, a gRPC API for internal RBAC predicate evaluation, an MCP tool catalogue (the security-sensitive subset), and a small admin CLI surface.

GraphQL subgraph (federated)

AUTH publishes a thin federated subgraph for cross-module identity queries. Sensitive writes (role grant, session revoke) remain on the admin REST API.

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key", "@external", "@shareable", "@requiresScopes"])

type Subject @key(fields: "id") {
  id: ID!
  tenantId: ID!
  kind: SubjectKind!
  email: String
  displayName: String!
  status: SubjectStatus!
  roles: [Role!]! @requiresScopes(scopes: [["auth.read"]])
  effectivePermissions: [String!]! @requiresScopes(scopes: [["auth.read"]])
  lastLoginAt: DateTime
  createdAt: DateTime!
}

type Role @key(fields: "code tenantId") {
  code: String!
  tenantId: ID!
  displayName: String!
  scopeLevel: ScopeLevel!
}

type Session @key(fields: "id") @requiresScopes(scopes: [["auth.session_read"]]) {
  id: ID!
  subjectId: ID!
  issuedAt: DateTime!
  expiresAt: DateTime!
  status: SessionStatus!
  device: Device
}

type Device {
  id: ID!
  label: String!
  trusted: Boolean!
  lastSeenAt: DateTime!
}

type AuthDecision @key(fields: "id") @requiresScopes(scopes: [["auth.audit_read"]]) {
  id: ID!
  subjectId: ID!
  action: String!
  resource: String!
  decision: Decision!
  reasonCode: String!
  ts: DateTime!
  brainChain: String!
}

enum SubjectKind { HUMAN AGENT SERVICE API_KEY }
enum SubjectStatus { ACTIVE LOCKED DISABLED }
enum ScopeLevel { PLATFORM TENANT PROJECT }
enum SessionStatus { ACTIVE REVOKED EXPIRED }
enum Decision { ALLOW DENY CHALLENGE }

type Query {
  me: Subject!
  subject(id: ID!): Subject
  decisions(subjectId: ID, since: DateTime, limit: Int = 50): [AuthDecision!]!
    @requiresScopes(scopes: [["auth.audit_read"]])
}

type Mutation {
  revokeSession(id: ID!): Boolean!
    @requiresScopes(scopes: [["auth.session_revoke"]])
  grantRole(subjectId: ID!, roleCode: String!, expiresAt: DateTime): Boolean!
    @requiresScopes(scopes: [["auth.role_grant"]])
  rotateApiKey(id: ID!): ApiKeyRotation!
    @requiresScopes(scopes: [["auth.api_key_rotate"]])
}

REST + OAuth surface (planned)

MethodPathPurpose
GET/.well-known/openid-configurationOIDC discovery document.
GET/.well-known/oauth-authorization-serverOAuth 2.1 metadata (RFC 8414).
GET/.well-known/jwks.jsonJWKS for JWT verification.
GET/oauth/authorizeAuthorisation endpoint (PKCE-required).
POST/oauth/tokenToken endpoint — authorization_code · refresh_token · client_credentials.
POST/oauth/revokeToken revocation (RFC 7009).
POST/oauth/introspectToken introspection (RFC 7662) — internal mTLS only.
POST/webauthn/register/beginStart passkey registration.
POST/webauthn/register/finishFinish passkey registration.
POST/webauthn/authenticate/beginStart passkey assertion.
POST/webauthn/authenticate/finishFinish passkey assertion.
POST/totp/enrollBegin TOTP enrolment (QR generation).
POST/totp/verifyVerify TOTP code.
POST/magic-link/requestRequest onboarding magic link.
GET/magic-link/consume?token=…Consume magic link.
GET/admin/sessionsList sessions (admin scope).
POST/admin/sessions/{id}/revokeRevoke session.
POST/admin/roles/grantGrant role.
POST/admin/sso/saml/configConfigure SAML IdP (P1).

gRPC RBAC API (internal)

syntax = "proto3";
package cyberos.auth.v1;

service RBAC {
  // Hot-path predicate. Sub-millisecond Redis cache.
  rpc Check(CheckRequest) returns (CheckResponse);
  // Batch variant for module gateways.
  rpc CheckBatch(CheckBatchRequest) returns (CheckBatchResponse);
  // Resolve effective permissions for a subject.
  rpc EffectivePermissions(SubjectRef) returns (PermissionSet);
}

message CheckRequest {
  string subject_jwt = 1;           // verified upstream
  string action = 2;                // "rew.write_run"
  string resource = 3;              // "rew/run/2026-05"
  map<string, string> attrs = 4;    // optional ABAC hints (P3+)
}

message CheckResponse {
  bool allow = 1;
  string reason_code = 2;           // "policy.role.cfo_required" | "ok"
  string brain_chain = 3;           // audit row chain hash
}

MCP tool catalogue

Tool nameInputsOutputsAnnotations
cyberos.auth.whoamiSubjectreadonly · scope=auth.read
cyberos.auth.check_permissionaction, resource{allow, reason}readonly
cyberos.auth.list_sessionssubject_idSession[]readonly · scope=auth.session_read
cyberos.auth.revoke_sessionsession_id{ok}destructive · scope=auth.session_revoke · human-confirm
cyberos.auth.grant_rolesubject_id, role_code, expires_at?{ok, audit_row}destructive · scope=auth.role_grant · human-confirm
cyberos.auth.list_decisionssubject_id?, since?, limitAuthDecision[]readonly · scope=auth.audit_read
cyberos.auth.rotate_api_keyapi_key_id{new_key, prefix}destructive · scope=auth.api_key_rotate
6

Key flows

Flow 1 — Human login with WebAuthn passkey

sequenceDiagram autonumber participant U as User (browser) participant SPA as CyberOS SPA participant A as AUTH /oauth/authorize participant W as AUTH /webauthn/* participant PG as PostgreSQL participant K as AWS KMS participant B as BRAIN audit U->>SPA: open app SPA->>A: GET /oauth/authorize?client=cyberos-spa&PKCE_S256=… A-->>SPA: redirect to login form SPA->>W: POST /webauthn/authenticate/begin {email} W->>PG: lookup credential by email PG-->>W: credential metadata W-->>SPA: assertion challenge SPA->>U: navigator.credentials.get(challenge) U->>SPA: signed assertion SPA->>W: POST /webauthn/authenticate/finish {assertion} W->>W: verify signature against stored public key W->>PG: update sign_count, last_seen_at W-->>A: subject_id authenticated A->>K: sign JWT (RS256, 15-min lifetime) K-->>A: signed access + refresh tokens A->>B: append auth.decision {subject, action:"login", decision:"allow"} A-->>SPA: 302 redirect with auth code SPA->>A: POST /oauth/token (code + PKCE verifier) A-->>SPA: {access_token, refresh_token} Note over SPA: subsequent calls carry Bearer JWT

WebAuthn replaces the password leg entirely for elevated roles (CEO / CFO / CHRO / CSO / CLO). TOTP remains as fallback for member-tier accounts ((FR pending)).

Flow 2 — TOTP MFA login (member tier)

sequenceDiagram autonumber participant U as User participant SPA as SPA participant A as AUTH /oauth/authorize participant P as AUTH /password participant T as AUTH /totp/verify participant K as KMS participant B as BRAIN U->>SPA: open app SPA->>A: GET /oauth/authorize A-->>SPA: login form U->>SPA: email + password SPA->>P: verify password (argon2id) P-->>SPA: 200 — TOTP required SPA-->>U: show TOTP prompt U->>SPA: 6-digit code SPA->>T: POST /totp/verify {code} T->>T: window-check (±1 step, replay-list check) alt valid + not replayed T-->>A: subject authenticated A->>K: sign JWT K-->>A: tokens A->>B: auth.decision {decision:"allow"} A-->>SPA: tokens else replayed code T->>B: auth.decision {decision:"deny", reason:"totp.replay"} T-->>SPA: 401 end

Flow 3 — Agent authentication (CUO persona-stamped token)

sequenceDiagram autonumber participant CUO as CUO router participant A as AUTH /oauth/token participant SC as scope.rs (Scope Contract resolver) participant BR as 🧠 BRAIN read participant K as KMS participant B as BRAIN write Note over CUO: CUO needs to invoke a skill on behalf of stephen@… CUO->>A: POST /oauth/token
grant_type=urn:cyberos:agent-impersonation
actor_jwt=cuo-service-jwt
on_behalf_of=stephen@…
persona_version=cuo-v2.3.1 A->>SC: resolve scope for persona cuo-v2.3.1 SC->>BR: read meta/persona/cuo/v2.3.1/scope.md BR-->>SC: {scope_set, valid_to} SC-->>A: effective scope ⊂ user's scope ∩ persona scope A->>K: sign JWT with claims {sub:stephen, act:cuo-v2.3.1, scope:…} K-->>A: signed token A->>B: auth.decision {subject:cuo, action:"impersonate", on_behalf_of:stephen, allow} A-->>CUO: {access_token, ttl:15min} Note over CUO: every downstream call carries this persona-stamped JWT;
BRAIN audit rows capture both `actor` and `on_behalf_of`.

The agent token's effective scope is the intersection of (a) what the user could do, (b) what the persona is granted, and (c) what AUTH's policy allows. Three-way narrowing means an agent can never exceed its user, nor its persona contract.

Flow 4 — RBAC predicate on cross-module call

sequenceDiagram autonumber participant M as Module (e.g. REW) participant AR as Apollo Router participant R as AUTH RBAC.Check (gRPC) participant RC as Redis cache participant PG as PostgreSQL participant B as BRAIN M->>AR: GraphQL mutation rewriteRun(…) AR->>AR: verify JWT signature + audience AR->>R: Check{subject_jwt, action:"rew.write_run", resource:"rew/run/2026-05"} R->>RC: GET subject_effective_permissions: alt cache hit RC-->>R: permission set else cache miss R->>PG: SELECT * FROM subject_effective_permission WHERE subject_id=… PG-->>R: rows R->>RC: SET subject_effective_permissions: TTL=15min end alt allowed R-->>AR: {allow:true, reason:"ok"} AR->>M: forward request R->>B: auth.decision (async) else denied R-->>AR: {allow:false, reason:"policy.role.cfo_required"} AR-->>M: 403 Forbidden R->>B: auth.decision {decision:"deny"} end

Hot-path latency budget: ≤ 8 ms p95 with cache hit, ≤ 25 ms p95 with miss. Audit write is fire-and-forget over a bounded channel.

Flow 5 — Service-account token-exchange (module-to-module)

sequenceDiagram autonumber participant SVC as CHAT service participant A as AUTH /oauth/token participant K as KMS participant B as BRAIN Note over SVC: CHAT needs to call BRAIN's put endpoint SVC->>A: POST /oauth/token
grant_type=client_credentials
client_id=chat-service
client_secret=…
scope=brain.put
audience=brain.cyberos.internal A->>A: verify client secret (argon2id) A->>A: check audience ∈ allowed_audiences A->>K: sign JWT {sub:chat-service, aud:brain, scope:brain.put, exp:15m} K-->>A: token A->>B: auth.decision {subject:chat-service, action:"token_exchange"} A-->>SVC: {access_token, ttl:900}

Service-to-service calls always use client_credentials with an audience claim — receiving services reject tokens not aimed at them, eliminating confused-deputy attacks.

7

Session lifecycle

A session traverses six states from issuance to expiry, with three terminal states (revoked, expired, replaced). Every transition writes an auth.decision row to BRAIN.

stateDiagram-v2 [*] --> Authenticating: user submits credentials Authenticating --> MFA_Required: password verified, 2FA outstanding Authenticating --> Locked: too many failures (≥ 5 in 10 min) MFA_Required --> Active: TOTP / WebAuthn assertion verified MFA_Required --> Locked: too many 2FA failures Active --> Refreshing: access_token within 1 min of expiry Refreshing --> Active: rotated refresh + new access issued Refreshing --> Revoked: reuse detected (refresh replay) Active --> Revoked: admin revoke OR impossible-travel challenge Active --> Expired: refresh_token TTL reached (30 d) Active --> Replaced: user logs in from a new device, old session optionally evicted Revoked --> [*] Expired --> [*] Replaced --> [*] Locked --> Active: lockout cleared (15 min auto OR admin unlock)

Token lifetime budget

Token typeLifetimeRotationNotes
Access token (JWT RS256)15 minutesnone (issue new)(FR pending) — short-lived
Refresh token30 daysevery use(FR pending) — replay invalidates session
OAuth authorization code60 secondssingle-usePKCE-bound
Magic link15 minutessingle-use(FR pending) — onboarding only
Agent persona token15 minuteseach tool call may refreshscope ⊂ user × persona × policy
API keyconfigurable (default 90 d)manualargon2id-hashed; ck_live_… prefix
WebAuthn credentialindefiniteuser-initiated revokesign_count tracked to detect cloning
8

Functional Requirements

The CyberOS FR catalogue is being rebuilt one feature at a time via the open fr-author Agent Skill.

Previous FR enumerations were archived 2026-05-14 and are no longer reflected on this page. PRD/SRS narrative remains authoritative for the spec; specific FRs land here as they are re-authored.

9

Non-Functional Requirements

PRD §11.2.3 security NFRs all flow through AUTH. Cross-referenced at nfr-catalog.html#auth.

NFR IDConcernTargetMeasurement
N(FR pending)Tenant data leakage incidents= 0 (sev-0)quarterly pen-test + automated cross-tenant test gate
N(FR pending)P1 base-salary system-reduction= 0 — legal commitmentpolicy gate on REW + audit replay
N(FR pending)Agent auto-act on irreversible op without confirm= 0 — runtime checkMCP gateway annotation check; CI test
N(FR pending)Prompt-injection exfiltration via email/document= 0 — CaMeL enforcedCaMeL test suite + red-team
N(FR pending)TLS for all inter-service trafficmTLS in cluster; HTTPS externalconfig inspection + Istio audit
N(FR pending)Penetration-test cadenceannual (P2+); after major releasescontract evidence
N(FR pending)Vulnerability remediation SLOCritical ≤ 24 h; High ≤ 7 dJIRA SLA tracking
N(FR pending)Sub-processor list public on Trust Centeralwaysweb-page diff alert
N(FR pending)OWASP Gen AI Top-10 mitigationsall addressedannual attestation
N(FR pending)RBAC predicate p95 (cache hit)≤ 8 msbench/rbac.rs · nightly
N(FR pending)RBAC predicate p95 (cache miss)≤ 25 msbench/rbac.rs · nightly
N(FR pending)OAuth token endpoint p95≤ 250 msk6 load test
N(FR pending)WebAuthn assertion verify p95≤ 50 msk6 load test
N(FR pending)AUTH availability (28-day)≥ 99.95%SLO monitor (N(FR pending))
N(FR pending)Auth-decision durability0 dropped rows under crashchaos test + BRAIN ledger walk
N(FR pending)Refresh-token reuse detection rate100% (zero false negatives)property-based test
N(FR pending)Session revoke propagation≤ 5 s end-to-end(FR pending) test
10

Dependencies

AUTH depends on three primitives (BRAIN for audit, OBS for telemetry, KMS for the signing key) and is depended on by every module that does anything cross-tenant or cross-module — which is to say, all of them.

graph LR subgraph upstream ["AUTH depends on"] BRAIN["🧠 BRAIN
auth.decision rows"] OBS["👁 OBS
traces + alerts"] KMS["🔑 AWS KMS
RS256 signing key"] PG["🗄 PostgreSQL
identity store"] REDIS["⚡ Redis 7
session + RBAC cache"] end AUTH["🔐 AUTH"] subgraph downstream ["Used by all 22 modules"] AR["Apollo Router
JWT verify @ edge"] MCP["🔌 MCP Gateway
tool-call gating"] AI["🧠 AI Gateway
per-tenant routing"] CHAT["💬 CHAT"] REW["💎 REW"] HR["👥 HR"] CRM["🏢 CRM"] OTHERS["…16 more"] end BRAIN --> AUTH OBS --> AUTH KMS --> AUTH PG --> AUTH REDIS --> AUTH AUTH --> AR AUTH --> MCP AUTH --> AI AUTH --> CHAT AUTH --> REW AUTH --> HR AUTH --> CRM AUTH --> OTHERS classDef shipped fill:#f5ede6,stroke:#45210e classDef planned fill:#fef6e0,stroke:#9c750a class BRAIN shipped class AUTH,AR,MCP,AI,CHAT,REW,HR,CRM,OTHERS,OBS planned class KMS,PG,REDIS shipped
11

Compliance scope

AUTH is the regulator's first stop. The audit chain it produces — co-owned with BRAIN — answers every identity, access, and breach-notification question across PDPL, GDPR, ISO 27001, and SOC 2.

Regulation / standardArticle / clauseAUTH feature that satisfies it
Vietnam PDPL (Law 91/2025)Art. 4 — Lawful processing basisEach subject row records consent basis; each auth.decision carries reason_code.
Vietnam PDPLArt. 14 — DSARcyberos-auth dsar-export bundles identity + decisions for a subject.
Vietnam Decree 13/2023Art. 17 — Personal data processing logauth.decision rows are the processing log for identity events.
Vietnam Decree 53/2022Art. 26 — Data localisation; breach notification ≤ 24hPer-tenant data-residency tag; auto-page MoPS template generator (planned P1).
GDPR (EU 2016/679)Art. 32 — Security of processingWebAuthn L3 · TLS 1.3 · KMS-wrapped signing · row-level security · Argon2id.
GDPRArt. 33 — Breach notificationauth.decision chain provides forensic timeline; alert templates auto-fill.
EU AI Act (Reg. 2024/1689)Art. 14 — Human oversightDestructive tool calls require human-confirm; agent JWTs cannot escalate.
ISO/IEC 27001:2022A.5.16 — Identity managementClosed role catalogue + scope contract grants.
ISO/IEC 27001:2022A.5.17 — Authentication informationArgon2id hashes; KMS-wrapped TOTP secrets.
ISO/IEC 27001:2022A.8.5 — Secure authenticationOAuth 2.1 + PKCE + MFA mandatory.
SOC 2 Type IICC6.1 — Logical accessRBAC predicate at every API boundary + audit chain.
SOC 2 Type IICC6.6 — Restricted accessRLS + scope contract grants narrow agents to a subset of users.
OWASP Gen AI Top-10 (2025)LLM02: Insecure output handlingMCP destructive-confirm gating routed through AUTH RBAC.
OWASP Gen AI Top-10 (2025)LLM08: Excessive agencyThree-way scope narrowing (user × persona × policy).
NIST SP 800-63BAAL2 / AAL3TOTP for AAL2; WebAuthn (phishing-resistant) for AAL3.
12

Risk entries

AUTH-specific risks tracked in the risk register. AUTH carries the highest single-module risk weight; one bug here is platform-wide.

IDRiskLikelihoodImpactOwnerMitigation
R-AUTH-001JWT signing-key compromiseLowCatastrophicCSOKMS-wrapped at rest; rotation every 90 d; old JWKS retained 30 d for verification.
R-AUTH-002Refresh-token replay leading to permanent session hijackLowHighCSORotation-on-use with reuse detection. Reuse → all sessions for that subject revoked.
R-AUTH-003Cross-tenant RLS bypass via subqueryLowCatastrophicCTORLS enabled on every table at migration time; CI test verifies cross-tenant read fails.
R-AUTH-004Agent JWT scope-escalation via prompt injectionMediumHighCSOScope is policy-resolved at AUTH not prompt-derived; injection cannot widen.
R-AUTH-005WebAuthn relying-party-ID mismatch breaks logins after domain changeLowMediumCTORP-ID stored per credential; migration tooling re-enrols passkeys with new RP-ID.
R-AUTH-006HIBP outage during signup blocks new usersMediumLowCTOHIBP check is best-effort; on timeout, log + allow with policy reviewer note.
R-AUTH-007TOTP secret leakage via DB backupLowHighCSOColumn-level KMS encryption; backups encrypted with separate keys.
R-AUTH-008OAuth implementation CVE (e.g., authorization-code injection)MediumHighCSOOAuth 2.1-only (mandatory PKCE); annual pen-test; subscribe to RFC drafts watchlist.
R-AUTH-009Compensation co-sign bypass via direct DB writeLowCatastrophicCSO(FR pending) enforced at REW boundary; direct DB write requires CSO+CFO co-sign; audit alarm.
R-AUTH-010Audit-chain write outage blinds the SOCLowHighCTOBounded local buffer + retry; alert on backlog > 60 s; AUTH refuses new logins after 5 min buffer.
13

KPIs

AUTH health rolls up into 9 KPIs covering authentication success, authorisation correctness, performance, and compliance posture.

KPIFormulaSourceTarget
Successful login ratelogins.success / logins.totalauth.decision≥ 99.5% / day
MFA challenge successmfa.success / mfa.totalauth.decision≥ 98%
RBAC predicate p95 (cache hit)Prometheus histogramOBS≤ 8 ms
Token endpoint p95histogramOBS≤ 250 ms
Refresh-reuse detectionscount / 28 dauth.decisiontracked; expect < 5/month
Impossible-travel challengescount / 28 dauth.decisiontracked; alert on > 50/day
Auth-decision durabilityrows_in_brain / rows_emittedchaos test100%
Session revoke p95 (end-to-end)histogramOBS≤ 5 s ((FR pending))
Penetration-test critical findingscountannual report= 0 critical · ≤ 2 high
14

RACI matrix

AUTH is owned by the CSO seat. Today (CSO vacant), the CEO is interim accountable with the CTO as engineering owner.

ActivityCEOCTOCSOCDOCLODPO
Service design + specACRICI
ImplementationIARIII
On-call rotationIRAIII
Penetration testingCCA/RIII
Role catalogue changesAIRICI
Key rotation (JWT signing)ICA/RIII
Incident response (auth breach)ARRCCR
Decree 53 MoPS notificationCIRIAR
DSAR fulfilment (auth scope)IICRCA

R Responsible · A Accountable · C Consulted · I Informed.

15

Planned CLI surface

A single admin CLI cyberos-auth for tenant operators. Every destructive command writes a chained audit row before exit.

1. Create a tenant

$ cyberos-auth tenant create \
    --slug stephen-personal \
    --display "Stephen Personal" \
    --country VN \
    --residency vn-hanoi-1

[tenant created]
  id:        01HZJ8R4M2K7QXP3F9D8YN7B2T
  slug:      stephen-personal
  residency: vn-hanoi-1
  audit:     brain seq=14823 chain=9f3e2a1b…8d4c

2. Enrol a passkey (interactive)

$ cyberos-auth webauthn enrol --subject stephen@cyberskill.com

→ open https://auth.cyberos.com/webauthn/enrol?token=eyJhbGc… on the device
✓ credential registered
  credential_id: AaJ9K… (TouchID, MacBook Pro 16, 2023)
  aaguid:        adce0002-35bc-c60a-648b-0b25f1f05503

3. Grant a role

$ cyberos-auth role grant \
    --subject acme-cfo@acme.com \
    --role cfo \
    --expires 2027-12-31T00:00:00Z

[role granted]  subject=acme-cfo@acme.com  role=cfo  expires=2027-12-31
[audit]         brain seq=14831 chain=b8d4…e3f7

4. Revoke a session

$ cyberos-auth session revoke --id 01HZJ8…JTC

[revoke]    session 01HZJ8…JTC → status=revoked
[propagated] Redis cache invalidated; Apollo Router will reject within 5 s
[audit]      brain seq=14832 chain=c9e5…f4a8

5. Read auth decisions for a subject (DSAR-style)

$ cyberos-auth decisions list --subject stephen@cyberskill.com --since 7d --format jsonl | head -3
{"ts":"2026-05-13T09:21:08Z","action":"login","decision":"allow","reason":"webauthn.aal3","device":"MacBook Pro 16"}
{"ts":"2026-05-13T09:21:09Z","action":"brain.put","decision":"allow","reason":"role.founder","resource":"memories/decisions/holdco-flip.md"}
{"ts":"2026-05-13T11:04:32Z","action":"rew.write_run","decision":"deny","reason":"policy.cfo_co_sign_required","resource":"rew/run/2026-05"}

6. Rotate the JWT signing key

$ cyberos-auth keys rotate --reason "quarterly-scheduled"

[rotate]   new key id: 2026-q2-sig
[kms]      old key id 2026-q1-sig retained for verification (30 d)
[jwks]     /.well-known/jwks.json updated; downstream propagation: ≤ 60 s
[audit]    brain seq=14841 chain=d1f3…a8c7

7. Export DSAR bundle

$ cyberos-auth dsar-export --subject acme-contact@acme.com --output dsar.zip

[dsar]  subject:    acme-contact@acme.com
[dsar]  identity:   1 row
[dsar]  decisions:  4,217 rows (28 d)
[dsar]  sessions:   12 rows
[dsar]  devices:    3 rows
[dsar]  webauthn:   0 credentials (none registered)
[dsar]  written:    dsar.zip (412 KB)
16

Phase status & estimates

Status
Planned
P0 design phase · M+1 start
Est. LoC (Rust)
~7,000
services/auth + sqlx migrations
Planned tests
120+
unit · integration · OAuth conformance
External libs
~12
axum · sqlx · webauthn-rs · totp-rs · rsa
CLI subcommands
~25 planned
cyberos-auth entrypoint
P0 budget
~$60/mo
RDS + Redis + Fargate
CapabilityStatus
OAuth 2.1 + PKCE token endpointplanned · P0
WebAuthn L3 (passkeys)planned · P0
TOTP MFAplanned · P0
RBAC predicate gRPC APIplanned · P0
JWT RS256 with KMS signingplanned · P0
Refresh-token rotation + reuse detectionplanned · P0
Service-account client_credentialsplanned · P0
Magic-link onboardingplanned · P0
Audit-chain integration (auth.decision)planned · P0
Agent token-exchange (persona-stamped)planned · P0
OIDC SSO (Google · M365 · Okta)planned · P1
Impossible-travel detectionplanned · P1
Device tracking + new-device emailplanned · P1
Decree 53 MoPS-notification automationplanned · P1
Multi-region active-activeplanned · P3+
SAML 2.0 enterprise SSOplanned · P2+
FIDO2 hardware-key attestationplanned · P2+
17

References

  • PRD §8.4 — Identity, authorisation server, OAuth 2.1 conventions.
  • PRD §8.6 — RBAC role catalogue (closed enum).
  • PRD §9.6 — (FR pending) through (FR pending) (PRD-tier).
  • PRD §11.2.3 — Security NFRs (SEC-001 through SEC-012).
  • SRS §4.6 — Formal (FR pending) through (FR pending) with verification methods.
  • Decree 53/2022/NĐ-CP (Vietnam) — Cybersecurity Law implementing decree; data-residency + breach notification.
  • Decree 13/2023/NĐ-CP (Vietnam) — Personal data processing protection.
  • Law 91/2025/QH15 (Vietnam PDPL) — Personal Data Protection Law.
  • RFC 6749 / draft-ietf-oauth-v2-1 — OAuth 2.1 specification.
  • RFC 7636 — Proof Key for Code Exchange (PKCE).
  • RFC 6238 — Time-Based One-Time Password.
  • RFC 7519 — JSON Web Token (JWT).
  • RFC 8414 — OAuth Authorization Server Metadata.
  • WebAuthn Level 3 — W3C Recommendation 2023.
  • NIST SP 800-63B — Digital Identity Authenticator Assurance Levels.
  • Architecture context: infrastructure.html#auth.