local_trusted
The default mode. Optimized for single-operator local use.
- Host binding: loopback only (localhost)
- Authentication: no login required
- Use case: local development, solo experimentation
- Board identity: auto-created local board user
authenticated
Login required. Supports two exposure policies.
authenticated + private
For private network access (Tailscale, VPN, LAN).
- Authentication: login required via Better Auth
- URL handling: auto base URL mode (lower friction)
- Host trust: private-host trust policy required
authenticated + public
For internet-facing deployment.
- Authentication: login required
- URL: explicit public URL required
- Security: stricter deployment checks in doctor
cloud_auth
Hosted controlled-beta multi-tenant mode. Every Company belongs to an
Organization (tenant); any signed-in user can self-serve-create an
Organization and becomes its owner.
- Authentication: login required (Google via Better Auth)
- Exposure: always
public, with an explicitauth.publicBaseUrl— the config schema rejects any other combination instance_adminpromotion: disabled everywhere. All four runtime promotion paths (both better-auth hooks, thebootstrap_ceoinvite branch, and board-claim) are gated by a single chokepoint,instanceAdminBootstrapEnabled(mode), which returnsfalseforcloud_auth.instance_adminis provisioned out-of-band only (break-glass/operator tooling) — never at runtime.- Self-serve Organizations:
POST /api/organizationslets any signed-in board user create an Organization and become its owner; company-create authorization is scoped to org role (owner/admin) rather than instance-admin status. - Board-claim: not available — board-claim is a single-tenant
local_trusted → authenticatedhandoff and is inert incloud_auth.
Execution Isolation (cloud_auth)
Oncloud_auth, every agent, crew, and Commander run — plus the host one-shot
CLIs (extraction, compaction, readiness probes) — executes inside a per-run E2B
sandbox, reached through the MCP broker. The control-plane host never runs
tenant model output directly. Self-hosted deployments (local_trusted and
single-tenant authenticated) are unchanged: they spawn host-direct with the
in-process MCP bridge, and the D1 unsandboxed-multitenant guard is a no-op.
Execution isolation by run kind
Every real dispatch passes a resolved
provider-sandbox execution target into
the D1 guard (assertUnsandboxedMultitenantAllowed). A null/local target
(acquire failed) still fails closed on cloud_auth — there is no silent host
fallback. The guard is a closed refuse-enumeration (refuses local +
docker-family, permits everything else), so a future tenant-operated
remote-tenant-runner is an allowed category by construction; the reserved
driver name (RESERVED_TENANT_RUNNER_DRIVER = "remote-runner") is documented
but not yet admitted in v1.
Instance experimental settings (Commander warm sandboxes)
These live on the singletoninstance_settings row (not env vars):
warmCommanderConversations(defaulttrue) — when true oncloud_auth, each active Commander conversation holds a warm (paused/resumed) per-conversation E2B sandbox across turns; the idle reaper + per-company cap bound accumulation. Setfalseto run Commander ephemeral-per-turn (create + destroy each turn), trading warm-disk continuity (codexresumevia~/.codex) for a smaller idle-VM footprint.enableWarmSandboxReaper+warmSandboxIdleTtlMinutes(default30) — the idle reaper destroys warm paused leases older than the TTL and evicts the oldest paused lease when a per-company cap is hit, bounding the per-user idle-VM cost surface the warm model introduces.
Commander-in-sandbox credential taxonomy
Extends the parent E2B credential taxonomy (cloud-execution-isolation spec §9) for the Commander run kind. Same posture org/crew already have — intra-company defense-in-depth, not a tenant boundary. Never enters the Commander VM (host-side only):DATABASE_URL /
DIRECT_DATABASE_URL, the secrets master key, GITHUB_PAT, BETTER_AUTH_SECRET
/ AOA_AGENT_JWT_SECRET (the key that mints the run-JWT), REDIS_URL, the
embeddings OPENAI_API_KEY, the operator ~/.claude login, and OAuth connector
refresh tokens / signed bundles (mcp:oauth:<id>).
Enters the Commander VM (scoped, short-lived): the per-turn Commander
run-JWT as AOA_API_KEY (company-bound, ~10 min TTL, dead after the turn), the
company’s own BYO model-provider API key (cloud shared-pool is API-key-only — no
subscription creds), and the company’s own connector access tokens
(AOA_MCP_*_TOKEN, short-lived, re-resolved fresh at every stage-in incl. warm
resume — never the stale paused-env token). The U5 env allowlist
(buildSandboxEnvAllowlist) is the sink that enforces this, keyed on the
model provider family (anthropic/openai/…) — not the E2B infra id.
Blast radius of a fully-compromised Commander turn: that ONE company’s own
tasks / goals / memory / artifacts (read/write within the driving user’s RBAC),
its own injected model key, its own connector access tokens. Cannot reach:
the control-plane DB, the secrets/signing keys, GITHUB_PAT, any other tenant’s
data, the operator login, or OAuth refresh tokens.
Threat-model notes:
- N-1 — the Commander run-JWT is a same-company bearer credential (parity
with the agent run-JWT), so its blast radius is “all same-company
assertCompanyAccess-gated routes,” bounded by the 10-min TTL + per-user + company scope — not broker-only. A leaked token is a same-company, time-boxed credential, not a cross-tenant one. - N-2 — the mint site derives
companyId/userId/userRoleserver-side from the authenticated session, never from client input, so a caller cannot widen its own company/role by crafting the token request.
Board Claim Flow
When migrating fromlocal_trusted to authenticated, set
AOA_HEADLESS_BOOTSTRAP=1 if local-board is still the only instance admin.
AoA then emits a one-time claim URL at startup:
- Promotes the current user to instance admin
- Demotes the auto-created local board admin
- Ensures active company membership for the claiming user
Changing Modes
Update the deployment mode:Security Headers (helmet + CSP)
AoA mounts helmet on every response. The exact header set depends on deployment mode:
CSP is skipped only when
AOA_DEPLOYMENT_MODE=local_trusted AND NODE_ENV !== "production". This is the Vite-HMR dev case — HMR’s runtime injects inline scripts and uses eval, both of which strict CSP would block. Loopback is the trust boundary in dev.
Strict-CSP directives:
connect-src 'self' is intentionally tight. If you wire a custom backend that fetches LLM endpoints from the browser (uncommon — most operators keep LLM calls server-side), you’ll need to extend the directive list in server/src/services/helmet-options.ts. Cross-Origin-Embedder-Policy (require-corp) stays disabled because it would block any external avatar/image without a CORP header.
When index.html changes, the inline-script hash auto-updates on next server start (no rebuild of the helmet config required). The hash extractor lives in server/src/services/csp-script-hashes.ts.