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.
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.
Humans, agents, service accounts, and API keys all materialise as Subject rows. Same predicate; different scopes.
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.
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.
What it does — 5W1H2C5M
A structured decomposition of AUTH's scope. Every cell traces back to PRD §8.6 + §9.6 + §11.2.3.
| Axis | Question | Answer |
|---|---|---|
| 5W · What | What 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 · Who | Who 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 · When | When 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 · Where | Where 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 · Why | Why 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 · How | How 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 · Cost | Cost 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 · Constraints | Constraints? | (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 · Materials | Stack? | 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 · Methods | Method 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 · Machines | Deployment? | 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 · Manpower | Who maintains? | 0.5 FTE today (covered by CEO). By P1: CSO seat owns 100% capacity + 24/7 on-call rotation with CTO. |
| 5M · Measurement | How 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+). |
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.
(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
| Component | Path (planned) | Responsibility |
|---|---|---|
oidc.rs | services/auth/src/oidc.rs | OIDC issuer — discovery doc, JWKS endpoint, ID-token issuance. Pure OpenID Connect Core 1.0 compliance. |
oauth.rs | services/auth/src/oauth.rs | OAuth 2.1 token endpoint — PKCE-required, audience-bound, refresh-token rotation, reuse detection. |
webauthn.rs | services/auth/src/webauthn.rs | WebAuthn L3 — credential creation, assertion verification, attestation parsing. Wraps webauthn-rs. |
totp.rs | services/auth/src/totp.rs | RFC 6238 TOTP — enrollment QR generation, code verification, replay window enforcement. |
rbac.rs | services/auth/src/rbac.rs | RBAC predicate evaluator. Input: (subject, action, resource). Output: allow / deny + audit record. Hot-path cache in Redis. |
scope.rs | services/auth/src/scope.rs | Scope Contract Grant resolver. Reads agent-persona scope sheets from BRAIN; emits effective scope set per JWT. |
session.rs | services/auth/src/session.rs | Session manager — issue, validate, revoke. Device tracking + impossible-travel detection. |
jwt.rs | services/auth/src/jwt.rs | JWT issuance / verification. RS256 via KMS; key rotation; JWKS publication. |
impossible_travel.rs | services/auth/src/impossible_travel.rs | Geographic-velocity check on consecutive logins (PRD (FR pending)); challenge if velocity > 1,000 km/h. |
password.rs | services/auth/src/password.rs | Argon2id hashing, password-policy enforcement, breach-list check via HIBP k-anonymity API. |
device.rs | services/auth/src/device.rs | Device fingerprinting ((FR pending)), new-device email, force-logout endpoint. |
magic_link.rs | services/auth/src/magic_link.rs | Magic-link onboarding flow ((FR pending)) — single-use, time-bound, onboarding-only. |
audit_bridge.rs | services/auth/src/audit_bridge.rs | Writes every decision to BRAIN via the canonical writer. Includes actor, action, resource, decision, reason. |
admin.rs | services/auth/src/admin.rs | Admin 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. |
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.
RBAC role catalogue (PRD §8.6.1 — closed)
| Role code | Display | Scope level | Examples of permissions |
|---|---|---|---|
founder | Founder / CEO | platform | All; sign authority. |
cfo | Chief Financial Officer | tenant | finance.*, rew.read_run (not write). |
chro | Chief Human Resources Officer | tenant | hr.*, rew.write_run (with CFO co-sign). |
cto | Chief Technology Officer | tenant | engineering.*, auth.admin. |
cso | Chief Security Officer | tenant | auth.*, obs.audit_read, key-rotation. |
clo | Chief Legal Officer | tenant | legal.*, brain.delete_purge_approve. |
dpo | Data Protection Officer | tenant | brain.dsar_export, auth.audit_read. |
cdo | Chief Data Officer | tenant | brain.admin, obs.read. |
cpo | Chief Product Officer | tenant | product.*, kb.write. |
cmo | Chief Marketing Officer | tenant | marketing.*, crm.read. |
cco | Chief Customer Officer | tenant | support.*, crm.write. |
member | Operating Member | tenant | Scope-narrowed per module; default chat.* + brain.read_own. |
contributor | External Contributor | project | Project-scoped read + comment only. |
client | Client User | project | Per-project shared workspace; no cross-tenant access. |
service | Service Account | tenant | client_credentials only; explicit audience. |
agent.cuo | CUO Agent | tenant | Routing only; never destructive without human gate. |
agent.skill | Skill Agent | tenant | Per-skill scope; capability-broker gated. |
agent.scheduled | Scheduled Task | tenant | Single-purpose; cron-defined scope. |
| + 4 more | (auditor, intern, vendor, partner) | tenant / project | See PRD §8.6.1. |
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)
| Method | Path | Purpose |
|---|---|---|
| GET | /.well-known/openid-configuration | OIDC discovery document. |
| GET | /.well-known/oauth-authorization-server | OAuth 2.1 metadata (RFC 8414). |
| GET | /.well-known/jwks.json | JWKS for JWT verification. |
| GET | /oauth/authorize | Authorisation endpoint (PKCE-required). |
| POST | /oauth/token | Token endpoint — authorization_code · refresh_token · client_credentials. |
| POST | /oauth/revoke | Token revocation (RFC 7009). |
| POST | /oauth/introspect | Token introspection (RFC 7662) — internal mTLS only. |
| POST | /webauthn/register/begin | Start passkey registration. |
| POST | /webauthn/register/finish | Finish passkey registration. |
| POST | /webauthn/authenticate/begin | Start passkey assertion. |
| POST | /webauthn/authenticate/finish | Finish passkey assertion. |
| POST | /totp/enroll | Begin TOTP enrolment (QR generation). |
| POST | /totp/verify | Verify TOTP code. |
| POST | /magic-link/request | Request onboarding magic link. |
| GET | /magic-link/consume?token=… | Consume magic link. |
| GET | /admin/sessions | List sessions (admin scope). |
| POST | /admin/sessions/{id}/revoke | Revoke session. |
| POST | /admin/roles/grant | Grant role. |
| POST | /admin/sso/saml/config | Configure 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 name | Inputs | Outputs | Annotations |
|---|---|---|---|
cyberos.auth.whoami | — | Subject | readonly · scope=auth.read |
cyberos.auth.check_permission | action, resource | {allow, reason} | readonly |
cyberos.auth.list_sessions | subject_id | Session[] | readonly · scope=auth.session_read |
cyberos.auth.revoke_session | session_id | {ok} | destructive · scope=auth.session_revoke · human-confirm |
cyberos.auth.grant_role | subject_id, role_code, expires_at? | {ok, audit_row} | destructive · scope=auth.role_grant · human-confirm |
cyberos.auth.list_decisions | subject_id?, since?, limit | AuthDecision[] | readonly · scope=auth.audit_read |
cyberos.auth.rotate_api_key | api_key_id | {new_key, prefix} | destructive · scope=auth.api_key_rotate |
Key flows
Flow 1 — Human login with WebAuthn passkey
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)
Flow 3 — Agent authentication (CUO persona-stamped 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
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)
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.
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.
Token lifetime budget
| Token type | Lifetime | Rotation | Notes |
|---|---|---|---|
| Access token (JWT RS256) | 15 minutes | none (issue new) | (FR pending) — short-lived |
| Refresh token | 30 days | every use | (FR pending) — replay invalidates session |
| OAuth authorization code | 60 seconds | single-use | PKCE-bound |
| Magic link | 15 minutes | single-use | (FR pending) — onboarding only |
| Agent persona token | 15 minutes | each tool call may refresh | scope ⊂ user × persona × policy |
| API key | configurable (default 90 d) | manual | argon2id-hashed; ck_live_… prefix |
| WebAuthn credential | indefinite | user-initiated revoke | sign_count tracked to detect cloning |
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.
Non-Functional Requirements
PRD §11.2.3 security NFRs all flow through AUTH. Cross-referenced at nfr-catalog.html#auth.
| NFR ID | Concern | Target | Measurement |
|---|---|---|---|
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 commitment | policy gate on REW + audit replay |
N(FR pending) | Agent auto-act on irreversible op without confirm | = 0 — runtime check | MCP gateway annotation check; CI test |
N(FR pending) | Prompt-injection exfiltration via email/document | = 0 — CaMeL enforced | CaMeL test suite + red-team |
N(FR pending) | TLS for all inter-service traffic | mTLS in cluster; HTTPS external | config inspection + Istio audit |
N(FR pending) | Penetration-test cadence | annual (P2+); after major releases | contract evidence |
N(FR pending) | Vulnerability remediation SLO | Critical ≤ 24 h; High ≤ 7 d | JIRA SLA tracking |
N(FR pending) | Sub-processor list public on Trust Center | always | web-page diff alert |
N(FR pending) | OWASP Gen AI Top-10 mitigations | all addressed | annual attestation |
N(FR pending) | RBAC predicate p95 (cache hit) | ≤ 8 ms | bench/rbac.rs · nightly |
N(FR pending) | RBAC predicate p95 (cache miss) | ≤ 25 ms | bench/rbac.rs · nightly |
N(FR pending) | OAuth token endpoint p95 | ≤ 250 ms | k6 load test |
N(FR pending) | WebAuthn assertion verify p95 | ≤ 50 ms | k6 load test |
N(FR pending) | AUTH availability (28-day) | ≥ 99.95% | SLO monitor (N(FR pending)) |
N(FR pending) | Auth-decision durability | 0 dropped rows under crash | chaos test + BRAIN ledger walk |
N(FR pending) | Refresh-token reuse detection rate | 100% (zero false negatives) | property-based test |
N(FR pending) | Session revoke propagation | ≤ 5 s end-to-end | (FR pending) test |
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.
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
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 / standard | Article / clause | AUTH feature that satisfies it |
|---|---|---|
| Vietnam PDPL (Law 91/2025) | Art. 4 — Lawful processing basis | Each subject row records consent basis; each auth.decision carries reason_code. |
| Vietnam PDPL | Art. 14 — DSAR | cyberos-auth dsar-export bundles identity + decisions for a subject. |
| Vietnam Decree 13/2023 | Art. 17 — Personal data processing log | auth.decision rows are the processing log for identity events. |
| Vietnam Decree 53/2022 | Art. 26 — Data localisation; breach notification ≤ 24h | Per-tenant data-residency tag; auto-page MoPS template generator (planned P1). |
| GDPR (EU 2016/679) | Art. 32 — Security of processing | WebAuthn L3 · TLS 1.3 · KMS-wrapped signing · row-level security · Argon2id. |
| GDPR | Art. 33 — Breach notification | auth.decision chain provides forensic timeline; alert templates auto-fill. |
| EU AI Act (Reg. 2024/1689) | Art. 14 — Human oversight | Destructive tool calls require human-confirm; agent JWTs cannot escalate. |
| ISO/IEC 27001:2022 | A.5.16 — Identity management | Closed role catalogue + scope contract grants. |
| ISO/IEC 27001:2022 | A.5.17 — Authentication information | Argon2id hashes; KMS-wrapped TOTP secrets. |
| ISO/IEC 27001:2022 | A.8.5 — Secure authentication | OAuth 2.1 + PKCE + MFA mandatory. |
| SOC 2 Type II | CC6.1 — Logical access | RBAC predicate at every API boundary + audit chain. |
| SOC 2 Type II | CC6.6 — Restricted access | RLS + scope contract grants narrow agents to a subset of users. |
| OWASP Gen AI Top-10 (2025) | LLM02: Insecure output handling | MCP destructive-confirm gating routed through AUTH RBAC. |
| OWASP Gen AI Top-10 (2025) | LLM08: Excessive agency | Three-way scope narrowing (user × persona × policy). |
| NIST SP 800-63B | AAL2 / AAL3 | TOTP for AAL2; WebAuthn (phishing-resistant) for AAL3. |
Risk entries
AUTH-specific risks tracked in the risk register. AUTH carries the highest single-module risk weight; one bug here is platform-wide.
| ID | Risk | Likelihood | Impact | Owner | Mitigation |
|---|---|---|---|---|---|
R-AUTH-001 | JWT signing-key compromise | Low | Catastrophic | CSO | KMS-wrapped at rest; rotation every 90 d; old JWKS retained 30 d for verification. |
R-AUTH-002 | Refresh-token replay leading to permanent session hijack | Low | High | CSO | Rotation-on-use with reuse detection. Reuse → all sessions for that subject revoked. |
R-AUTH-003 | Cross-tenant RLS bypass via subquery | Low | Catastrophic | CTO | RLS enabled on every table at migration time; CI test verifies cross-tenant read fails. |
R-AUTH-004 | Agent JWT scope-escalation via prompt injection | Medium | High | CSO | Scope is policy-resolved at AUTH not prompt-derived; injection cannot widen. |
R-AUTH-005 | WebAuthn relying-party-ID mismatch breaks logins after domain change | Low | Medium | CTO | RP-ID stored per credential; migration tooling re-enrols passkeys with new RP-ID. |
R-AUTH-006 | HIBP outage during signup blocks new users | Medium | Low | CTO | HIBP check is best-effort; on timeout, log + allow with policy reviewer note. |
R-AUTH-007 | TOTP secret leakage via DB backup | Low | High | CSO | Column-level KMS encryption; backups encrypted with separate keys. |
R-AUTH-008 | OAuth implementation CVE (e.g., authorization-code injection) | Medium | High | CSO | OAuth 2.1-only (mandatory PKCE); annual pen-test; subscribe to RFC drafts watchlist. |
R-AUTH-009 | Compensation co-sign bypass via direct DB write | Low | Catastrophic | CSO | (FR pending) enforced at REW boundary; direct DB write requires CSO+CFO co-sign; audit alarm. |
R-AUTH-010 | Audit-chain write outage blinds the SOC | Low | High | CTO | Bounded local buffer + retry; alert on backlog > 60 s; AUTH refuses new logins after 5 min buffer. |
KPIs
AUTH health rolls up into 9 KPIs covering authentication success, authorisation correctness, performance, and compliance posture.
| KPI | Formula | Source | Target |
|---|---|---|---|
| Successful login rate | logins.success / logins.total | auth.decision | ≥ 99.5% / day |
| MFA challenge success | mfa.success / mfa.total | auth.decision | ≥ 98% |
| RBAC predicate p95 (cache hit) | Prometheus histogram | OBS | ≤ 8 ms |
| Token endpoint p95 | histogram | OBS | ≤ 250 ms |
| Refresh-reuse detections | count / 28 d | auth.decision | tracked; expect < 5/month |
| Impossible-travel challenges | count / 28 d | auth.decision | tracked; alert on > 50/day |
| Auth-decision durability | rows_in_brain / rows_emitted | chaos test | 100% |
| Session revoke p95 (end-to-end) | histogram | OBS | ≤ 5 s ((FR pending)) |
| Penetration-test critical findings | count | annual report | = 0 critical · ≤ 2 high |
RACI matrix
AUTH is owned by the CSO seat. Today (CSO vacant), the CEO is interim accountable with the CTO as engineering owner.
| Activity | CEO | CTO | CSO | CDO | CLO | DPO |
|---|---|---|---|---|---|---|
| Service design + spec | A | C | R | I | C | I |
| Implementation | I | A | R | I | I | I |
| On-call rotation | I | R | A | I | I | I |
| Penetration testing | C | C | A/R | I | I | I |
| Role catalogue changes | A | I | R | I | C | I |
| Key rotation (JWT signing) | I | C | A/R | I | I | I |
| Incident response (auth breach) | A | R | R | C | C | R |
| Decree 53 MoPS notification | C | I | R | I | A | R |
| DSAR fulfilment (auth scope) | I | I | C | R | C | A |
R Responsible · A Accountable · C Consulted · I Informed.
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)
Phase status & estimates
cyberos-auth entrypoint| Capability | Status |
|---|---|
| OAuth 2.1 + PKCE token endpoint | planned · P0 |
| WebAuthn L3 (passkeys) | planned · P0 |
| TOTP MFA | planned · P0 |
| RBAC predicate gRPC API | planned · P0 |
| JWT RS256 with KMS signing | planned · P0 |
| Refresh-token rotation + reuse detection | planned · P0 |
| Service-account client_credentials | planned · P0 |
| Magic-link onboarding | planned · P0 |
| Audit-chain integration (auth.decision) | planned · P0 |
| Agent token-exchange (persona-stamped) | planned · P0 |
| OIDC SSO (Google · M365 · Okta) | planned · P1 |
| Impossible-travel detection | planned · P1 |
| Device tracking + new-device email | planned · P1 |
| Decree 53 MoPS-notification automation | planned · P1 |
| Multi-region active-active | planned · P3+ |
| SAML 2.0 enterprise SSO | planned · P2+ |
| FIDO2 hardware-key attestation | planned · P2+ |
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.