Security model
The console is a thin client. It owns no backend, stores no identities, and proxies no traffic. Every request flows from your browser to the AcasoOS instance you connected to.
The security model rests on three primitives:
- Session-key exchangelong-lived keys are traded for short-lived session tokens.
- Strict CORSevery instance must whitelist the console's origin.
- Cascading revocationrevoking the parent kills every derived session.
Session-key exchange
Long-lived API keys — each owned by an application — are the platform's most-privileged credential. The console never persists one. The exchange runs once per session and immediately downgrades the long-lived key to a short-lived child.
- Submit the form. You paste the AcasoOS URL and the long-lived admin key into the Connect form.
- Exchange the key. The console calls
POST {url}/v1/auth/sessionwithAuthorization: Bearer <long-lived-key>. - AcasoOS mints a child key. A new row lands in
dh_api_keyswithkind='session',parent_key_id=<long-lived-key.id>, inherited scopes, andexpires_at = NOW() + 8h. - Receive the session. The response is
{session_token, expires_at, user, tenant}. - Discard the long-lived key. It is never written to
sessionStorage,localStorage,IndexedDB, or cookies. - Persist the session. The console writes
{url, name, session_token, expires_at, user, tenant}tosessionStorage(notlocalStorage). - Authorize everything else. Every subsequent call sends
session_tokenas the Bearer token.
Tip: Verify this yourself in DevTools → Application → Storage. The session token appears in
sessionStorage; the long-lived key is absent from every storage tier.
Cascading revocation
A session key inherits its scopes and bound user from its parent. The resolve_api_key_by_prefix SQL function joins both rows, so a session whose parent is revoked or expired is rejected at the next database lookup.
Redis publishes keyid.invalidate <parent_id> on the acaso:key:invalidated channel; cached entries TTL-expire within roughly 60 seconds across replicas.
Warning: Revoking the parent kills every live session derived from it. This is the right reason to give each human their own key rather than sharing one across a team.
Strict CORS
Every AcasoOS instance must list the console's origin in ACASO_CONSOLE_ORIGINS. Wildcard * is rejected at app startup with a clear configuration error. The CORS layer applies uniformly to every endpoint, including /v1/auth/session.
See Reference: Environment variables for defaults.
What the console does not use
localStoragefor session tokens.sessionStorageis cleared on tab close, bounding the persistent attack surface.- Cookies for credentials. All auth flows as
Authorization: Bearerheaders — no CSRF surface. - Refresh tokens in v1. You re-paste your long-lived key once per workday. A future spec may add refresh tokens behind an
HttpOnlycookie.
Defense in depth
Console-side scope-gating (sidebar visibility, button enablement) is ergonomic only. The authoritative authorization decisions live in the database via row-level security and scope checks. A penetration test that bypassed the console gates would still meet the same enforcement at the API layer.