Org in a Box
Architecture

Security Architecture

Encryption, authentication, authorisation, and compliance posture.

Encryption at Rest

Keyring (AES-256-GCM)

All secrets stored in the database (LLM API keys, OAuth tokens) are encrypted with AES-256-GCM before insert.

Ciphertext format: v1:<12-byte IV (base64)>:<auth tag (base64)>:<ciphertext (base64)>

The 32-byte master key lives in:

  • Production: Azure Key Vault (orginabox-keyring secret)
  • Local: ~/.orginabox/.keyring (file mode 0600)

On Azure, the API and worker containers use a user-assigned managed identity with Key Vault Secrets User role — no credentials in environment variables.

The current Azure preview still leaves Key Vault on public networking with Azure-services bypass enabled. Secret access is identity-gated, but private endpoints / locked-down network ACLs are still a follow-up before calling the hosted path production-grade.

Database

  • PostgreSQL TLS enforced in Azure (Flexible Server with sslmode=require)
  • Backups encrypted at rest by Azure

Authentication

JWTs

HMAC-SHA256 JWTs derived from the keyring:

TokenTTLClaims
Access token15 minutessub, tid, roles, iat, exp
Refresh token7 daysStored encrypted in entra_sessions

Azure AD SSO (PKCE flow)

  1. Browser → /v1/auth/login → redirect to login.microsoftonline.com
  2. User authenticates with Microsoft
  3. Callback → exchange code for tokens → extract tid, oid, and user identity from the id_token
  4. Find-or-create tenant + user rows
  5. Seeded roles and tenant provisioning are applied in-app
  6. Issue OIAB JWT, store refresh token encrypted

Automatic Azure AD group-to-role mapping remains a planned follow-up, not a fully wired part of the checked-in callback flow.

Split app registrations (SaaS prod)

Customer and platform traffic route through two distinct Entra app registrations so they can be scoped and audited independently:

  • Customer app reg (AZURE_CLIENT_ID, multi-tenant): handles /v1/auth/* for customer tenant admins and end users. Tenant ID typically organizations or common. Redirect: /v1/auth/callback.
  • Platform app reg (AZURE_PLATFORM_CLIENT_ID, single-tenant, appRoleAssignmentRequired=true): handles /v1/platform/auth/* for internal staff only, gated on membership in the platform-admins security group. Tenant ID MUST be the specific platform tenant ID (never common). Redirect: /v1/platform/auth/callback.

Each platform variable (AZURE_PLATFORM_CLIENT_ID, AZURE_PLATFORM_CLIENT_SECRET, AZURE_PLATFORM_TENANT_ID) independently falls back to its AZURE_* counterpart when unset. Self-hosted single-tenant pilots can omit the platform variables entirely and use one app reg for both flows — the split only matters in SaaS prod where customer-facing sign-in and internal staff sign-in must not share an identity.

Per-invite authority pinning

When a first-admin invite is being claimed, /v1/auth/login accepts an optional invite=<token> query param. If the token matches a pending, unexpired tenant_admin_invites row whose tenant has a provisioned entra_tenant_id, the login handler overrides AZURE_TENANT_ID just for that sign-in and uses the invite tenant's directory as the authorize authority (the same authority is re-resolved server-side on the callback for token exchange). This lets AZURE_TENANT_ID=organizations stay the multi-tenant SaaS default while still letting Azure's Home Realm Discovery route B2B guest emails whose domain isn't a verified Azure tenant. Unknown, expired, or revoked invites silently fall back to the env-configured authority — no information is leaked about token validity. The raw invite token is never placed in the Entra authorize or callback URL.

Legacy API Key

When AZURE_CLIENT_ID is unset, Authorization: Bearer <ORGINABOX_API_KEY> is accepted for all API requests. This is single-operator mode.

Authorisation (RBAC)

Every API route runs the auth middleware which:

  1. Verifies the JWT signature and expiry
  2. Loads the user's roles from user_roles + roles
  3. Computes effective permissions (union of all roles)
  4. Sets c.set("auth", { user, tenant, roles, rbac })

Route handlers call rbac.agents.create, rbac.admin.view_audit_log, etc. before executing.

Network Security

  • All inter-service traffic stays within the Docker / Container Apps network
  • Only these ports are publicly exposed: 8787 (API), 3000 (web), gateways
  • In Azure, Container Apps ingress uses mTLS termination
  • In the checked-in Azure preview, Key Vault is still publicly reachable and not yet private-endpoint-only
  • noVNC (:6080) should be firewalled in production — it gives visual access to the sandbox

Sandbox Isolation

Agents execute inside an Ubuntu 24.04 container:

  • No access to host filesystem (volume mounts are read-only config only)
  • Network egress is unrestricted by default (configure Docker network policies to restrict)
  • Each user's workspace is a separate Docker volume

Compliance Checklist

ControlStatus
Encryption in transitTLS everywhere
Encryption at restAES-256-GCM for secrets; Azure disk encryption for DB
AuthenticationAzure AD SSO + HMAC-SHA256 JWTs
AuthorisationRBAC on every endpoint
Audit loggingAll actions logged with user + tenant + IP
Audit log retention2 years for auth/data, 1 year for tool/agent
Secret managementAzure Key Vault in production
Input validationZod on all API request bodies
SQL injectionParameterised queries via Drizzle ORM
React renderingServer components; content rendered via MDX, not raw HTML injection

On this page