Multi-Tenancy Architecture
Arbitex is a multi-tenant system. Each customer organization (org) is a fully isolated tenant. This reference describes the isolation model, per-org configuration surface, data isolation guarantees, and how the Hybrid Outpost extends tenancy to on-premises deployments.
Tenant identity
Section titled “Tenant identity”An org (organization) is the top-level tenant unit in Arbitex. Every org has a UUID, referred to throughout the system as org_id or tenant_id. These identifiers are interchangeable — tenant_id is the field name used in database models; org_id is used in API paths.
There is no organizations table — org identity is carried by tenant_id on every scoped resource. All data access is filtered by tenant_id at the query layer. There is no application-level object that “owns” a tenant; isolation is enforced by the presence of tenant_id on every resource.
Tenant isolation model
Section titled “Tenant isolation model”tenant_id scoping on all resources
Section titled “tenant_id scoping on all resources”Every resource that is tenant-scoped carries a tenant_id (UUID) column. Resources are isolated at the database query layer — every query that accesses tenant data filters by tenant_id == current_user.tenant_id. Cross-tenant access is structurally prevented by this pattern.
Tenant-scoped resources:
| Resource | Table | Isolation field |
|---|---|---|
| Users | users | tenant_id |
| Groups | groups | tenant_id |
| API keys | api_keys | tenant_id |
| Audit log entries | audit_logs | tenant_id |
| IP allowlist entries | ip_allowlist_entries | tenant_id |
| Policy chains | policy_pack_chains | scope_id (org UUID) |
| Policy packs | policy_packs | tenant_id |
| Policy rules | policy_rules | (scoped via pack → chain) |
| DLP rules | org_dlp_rules | org_id |
| Compliance bundle bindings | org_compliance_bundles | org_id |
| SCIM tokens | org_scim_tokens | org_id |
| Outpost audit sync entries | audit_logs | tenant_id + outpost_id |
Authentication and tenant context
Section titled “Authentication and tenant context”When a user authenticates (JWT or API key), the platform loads the tenant_id from the user record and places it in the request scope state. This value flows through all middleware and route handlers as the authoritative tenant identifier for the request.
The require_admin dependency verifies that the user’s role == "admin" and that their tenant_id matches the resource being accessed. Admin privileges are scoped to a single tenant — there is no global admin role in the application layer.
Audit log isolation
Section titled “Audit log isolation”The audit log search API (GET /api/admin/audit-logs/) enforces tenant scoping explicitly:
base_query = select(AuditLog).where(AuditLog.tenant_id == current_user.tenant_id)An admin cannot query audit entries for any other org. The tenant_id filter is applied before any user-supplied filter parameters — it cannot be overridden by the API caller.
Per-org configuration
Section titled “Per-org configuration”Each org has an independent configuration surface. No configuration is shared between orgs.
Policy chains and packs
Section titled “Policy chains and packs”Policy evaluation uses two chain scopes:
scope="org"— the org-level policy chain, applied to all users in the orgscope="user"— per-user override chain (personal policy adjustments)
The org chain is the primary enforcement mechanism. Rules use user_groups conditions to target specific groups within the org. Group membership is a condition on rules, not a separate chain scope.
Policy packs (bundles) are either:
- Compliance bundles — pre-built packs locked for editing (
is_bundle=true) - Custom packs — org-created packs with admin-editable rules
Policy chains and packs are scoped to tenant_id. A pack created by one org is invisible to all other orgs.
DLP rules
Section titled “DLP rules”Org-level DLP rules (org_dlp_rules table, org_id column) layer on top of platform-level patterns. Each org can add custom detection patterns without affecting other tenants. Platform DLP patterns are global (no tenant_id) and run before org-specific rules.
Provider access and model routing
Section titled “Provider access and model routing”Model access controls are configured per group within an org (group_model_access table). Routing rules (routing_rules table) are scoped to tenant_id. An org’s routing rules, fallback chains, and latency thresholds are independent of all other orgs.
Budget and quota controls
Section titled “Budget and quota controls”Usage quotas (quota table) are scoped per-org. Token budgets, daily request limits, and cost caps apply within a single org’s usage counters (org_usage_counter table, org_id column). Usage from one org does not consume quota from another.
SCIM provisioning
Section titled “SCIM provisioning”Per-org SCIM tokens (org_scim_tokens table, org_id column) are stored as bcrypt hashes. Each org has its own SCIM bearer token. Token rotation on one org does not affect any other org’s provisioning. See SCIM provisioning guide for configuration details.
IP allowlist
Section titled “IP allowlist”Per-org IP allowlist entries (ip_allowlist_entries.tenant_id) are checked in isolation. An org with IP restrictions does not affect access for users in other orgs. An org with no allowlist entries permits all source IPs, regardless of what other orgs have configured.
Data isolation guarantees
Section titled “Data isolation guarantees”What is isolated per-org
Section titled “What is isolated per-org”| Data type | Isolation mechanism |
|---|---|
| Conversations and messages | tenant_id on conversations and messages tables |
| Audit trail | tenant_id filter on all queries; HMAC chain is per-org sequence |
| API keys | tenant_id on api_keys; keys can only authenticate users in the same org |
| Groups and memberships | tenant_id on groups; group policies apply only within the org |
| Users | tenant_id on users; cross-org user queries are not possible |
| Policy engine configuration | Chain scope uses org UUID as scope_id |
| DLP configuration | org_id on custom rules; org_id on compliance bundle bindings |
| SIEM configuration | tenant_id on SIEM connector configs |
| Compliance bundle state | Per-org enable/disable binding in org_compliance_bundles |
What is shared across orgs (platform-level)
Section titled “What is shared across orgs (platform-level)”| Shared resource | Notes |
|---|---|
| Platform-level DLP patterns | dlp_rules without org_id — available to all orgs, read-only |
| Compliance bundle definitions | Bundle templates are global; per-org bindings activate them |
| Model catalog | Provider configurations and model availability are platform-level |
| GeoIP / CredInt databases | Shared MaxMind and CredInt Bloom filter databases |
| Infrastructure | Kubernetes cluster, PostgreSQL instance (separate databases per environment, row-level tenant isolation within tables) |
What is NOT stored per-org
Section titled “What is NOT stored per-org”Arbitex does not store prompt or completion content by default. When prompt_text and response_text appear in audit entries, they are present only when prompt retention is explicitly enabled for the org. The default behavior is to omit this content from audit entries — only the metadata (token counts, model, provider, action, DLP category match) is retained.
Cross-tenant protections
Section titled “Cross-tenant protections”Middleware enforcement
Section titled “Middleware enforcement”IP allowlist middleware reads org context from scope["state"]["org_id"], which is set by authentication middleware from the authenticated user’s tenant_id. An attacker cannot specify a different org_id — it is derived from the authenticated JWT or API key, not from request parameters.
Query filtering
Section titled “Query filtering”No query in the platform codebase uses unbounded access to tenant-scoped tables. All queries that access users, groups, audit logs, policy configurations, or API keys include an explicit tenant_id filter as part of the base query construction. The filter is applied before any user-supplied filters.
Admin dependency
Section titled “Admin dependency”The require_admin FastAPI dependency enforces both role and tenant scope. It:
- Verifies the JWT is valid and the user exists
- Checks
user.role == "admin" - Loads the user’s
tenant_idfor downstream use in query scoping
A user with role=admin in org A cannot access resources in org B — the tenant_id carried in their JWT anchors all queries to their own org.
SCIM token isolation
Section titled “SCIM token isolation”Per-org SCIM tokens (org_scim_tokens) use org_id as the scope key. Token verification looks up only WHERE org_id = ? for the specific org in the path parameter. Presenting a valid SCIM token for org A against org B’s SCIM endpoint returns 401.
Org lifecycle
Section titled “Org lifecycle”Creation
Section titled “Creation”Org creation assigns a UUID as the tenant_id. Initial configuration:
- No users (first admin is invited post-creation)
- No policy chain (platform default ALLOW applies)
- No DLP configuration
- No compliance bundles active
- No IP allowlist (all IPs permitted)
Configuration
Section titled “Configuration”After creation, org admins:
- Configure authentication (SAML IdP, OIDC, or local)
- Set up SCIM provisioning (optional)
- Create groups and assign users
- Activate compliance bundles and build policy chains
- Configure model access and routing rules
- Optionally configure IP allowlisting
Configuration changes (policy updates, user management, compliance bundle toggles) are audit-logged with the admin user ID and timestamp.
Suspension
Section titled “Suspension”When an org is suspended, user.is_active = false is set for all users in the org. API keys and SCIM tokens continue to exist in the database but authentication fails because user accounts are inactive. Data is retained intact for the retention period.
Data deletion
Section titled “Data deletion”On org deletion, all tenant-scoped rows are removed (cascades and SET NULL foreign keys preserve audit log integrity — user_id is set to NULL on audit entries rather than deleting the entries).
Hybrid Outpost tenant model
Section titled “Hybrid Outpost tenant model”The Hybrid Outpost is a self-hosted Gateway deployment that connects to the Arbitex Cloud control plane. Each Outpost instance maps to a single org via the org’s tenant_id.
Registration
Section titled “Registration”When an Outpost registers with the Cloud control plane (POST /cloud/api/outposts/register), it is issued a client certificate with tenant_id embedded. Subsequent communications from the Outpost present this certificate for mTLS authentication. The tenant_id in the certificate anchors all Outpost operations to the correct org.
Policy sync
Section titled “Policy sync”The Cloud control plane syncs the org’s policy chain to the Outpost on registration and on each heartbeat (PUT /cloud/api/outposts/{outpost_id}/heartbeat). The policy chain delivered is exactly the chain for the Outpost’s org — no other org’s policies are ever sent to an Outpost instance.
Audit sync
Section titled “Audit sync”Outpost-generated audit events are synced to the Cloud control plane and stored with:
tenant_id= the org’s UUID (same as cloud-generated entries)source = "outpost"outpost_id= the Outpost instance UUID
The HMAC chain covers Outpost-synced entries identically to cloud entries. The audit log presents a unified, chronologically ordered view of all events for the org regardless of origin.
Data residency
Section titled “Data residency”In an air-gap deployment, Outpost audit events remain on-premises until sync occurs. For organizations with data residency requirements, the audit sync interval can be configured, or sync can be disabled entirely (the Outpost then provides local-only audit access via its own API).
See also
Section titled “See also”- Request lifecycle architecture — End-to-end request flow through tenant boundaries
- SCIM provisioning guide — Automated user provisioning per org
- IP Allowlist Administration — Per-org IP restriction configuration
- Audit Data Model Reference — Audit log schema and tenant_id scoping
- Security Overview — Cross-cutting security controls
- Outpost architecture — Hybrid Outpost deployment model