Multi-Tenant Architecture

Enterprise-Grade Isolation with Zero Compromise


Overview

Archivus implements a sophisticated multi-tenant architecture with 5 layers of security ensuring complete data isolation. Every query, every AI interaction, and every operation is scoped to your tenant, mathematically guaranteeing data separation.


5-Layer Security Model

┌─────────────────────────────────────────────────────────────────┐
│                     Incoming Request                             │
└──────────────────────────────┬──────────────────────────────────┘
                               │
┌──────────────────────────────▼──────────────────────────────────┐
│  Layer 1: Authentication                                         │
│  ├─ JWT Token Verification (Supabase Auth)                      │
│  ├─ API Key Validation                                          │
│  └─ Session Verification                                        │
└──────────────────────────────┬──────────────────────────────────┘
                               │
┌──────────────────────────────▼──────────────────────────────────┐
│  Layer 2: Authorization                                          │
│  ├─ Role-Based Access Control (RBAC)                            │
│  ├─ Resource Permissions                                        │
│  └─ Feature Tier Verification                                   │
└──────────────────────────────┬──────────────────────────────────┘
                               │
┌──────────────────────────────▼──────────────────────────────────┐
│  Layer 3: Tenant Context                                         │
│  ├─ Tenant ID Extraction from Auth                              │
│  ├─ Request Context Injection                                   │
│  └─ Cross-Tenant Request Rejection                              │
└──────────────────────────────┬──────────────────────────────────┘
                               │
┌──────────────────────────────▼──────────────────────────────────┐
│  Layer 4: Service Layer Validation                               │
│  ├─ Business Logic Tenant Checks                                │
│  ├─ Resource Ownership Verification                             │
│  └─ AI Context Filtering                                        │
└──────────────────────────────┬──────────────────────────────────┘
                               │
┌──────────────────────────────▼──────────────────────────────────┐
│  Layer 5: Database RLS (Row-Level Security)                      │
│  ├─ PostgreSQL RLS Policies on 40+ Tables                       │
│  ├─ Automatic Query Filtering                                   │
│  └─ Database-Level Backstop                                     │
└──────────────────────────────┬──────────────────────────────────┘
                               │
┌──────────────────────────────▼──────────────────────────────────┐
│                     Your Isolated Data                           │
└─────────────────────────────────────────────────────────────────┘

Layer Details

Layer 1: Authentication

Every request must be authenticated:

JWT Authentication:

// Token validation flow
token := extractToken(request)
claims, err := supabase.VerifyJWT(token)
if err != nil {
    return Unauthorized("Invalid token")
}
// Extract tenant_id from claims
tenantID := claims.TenantID

API Key Authentication:

// API key validation
apiKey := request.Header.Get("X-API-Key")
keyRecord, err := validateAPIKey(apiKey)
if err != nil {
    return Unauthorized("Invalid API key")
}
// Tenant embedded in key
tenantID := keyRecord.TenantID

Layer 2: Authorization

Role-based access control:

Role Permissions
owner Full access, billing, user management
admin All features, user management
editor Create, read, update documents
viewer Read-only access
// Permission check
if !hasPermission(user, "documents:write") {
    return Forbidden("Insufficient permissions")
}

Layer 3: Tenant Context

Every request carries tenant context:

// Middleware injects tenant context
func TenantMiddleware(c *gin.Context) {
    tenantID := extractTenantID(c)

    // Set in request context
    ctx := context.WithValue(c.Request.Context(), "tenant_id", tenantID)
    c.Request = c.Request.WithContext(ctx)

    c.Next()
}

Layer 4: Service Layer Validation

Business logic re-validates ownership:

func (s *DocumentService) GetDocument(ctx context.Context, docID string) (*Document, error) {
    tenantID := ctx.Value("tenant_id").(string)

    doc, err := s.repo.FindByID(ctx, docID)
    if err != nil {
        return nil, err
    }

    // Service-level tenant check (defense in depth)
    if doc.TenantID != tenantID {
        return nil, ErrNotFound // Don't leak existence
    }

    return doc, nil
}

Layer 5: Database RLS

PostgreSQL Row-Level Security as the final backstop:

-- Example RLS policy on documents table
CREATE POLICY "tenant_isolation" ON documents
    FOR ALL
    USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- Every query automatically filtered
SELECT * FROM documents;
-- Becomes: SELECT * FROM documents WHERE tenant_id = '...'

RLS Coverage

Tables with RLS Policies (40+)

Category Tables
Core documents, folders, tags, collections
AI chat_sessions, chat_messages, ai_analysis
Collaboration shares, invitations, workspaces
Automation pipelines, pipeline_runs, workflows
DAG dag_workflows, dag_executions, dag_tasks
Audit audit_logs, api_key_usage, ai_traffic_logs
Settings tenant_settings, user_preferences, mcp_servers

RLS Policy Pattern

-- Standard tenant isolation policy
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;

CREATE POLICY "tenant_isolation" ON table_name
    FOR ALL
    USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- Insert policy ensures tenant_id is set
CREATE POLICY "tenant_insert" ON table_name
    FOR INSERT
    WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);

AI Isolation

Context Filtering

AI never receives cross-tenant data:

func (s *ArchieService) Chat(ctx context.Context, message string) (*Response, error) {
    tenantID := ctx.Value("tenant_id").(string)

    // Search only within tenant
    relevantDocs, err := s.searchService.Search(ctx, SearchParams{
        Query:    message,
        TenantID: tenantID,  // Always filtered
        Limit:    10,
    })

    // Build AI context from tenant-filtered results only
    context := buildContext(relevantDocs)

    // Send to Claude
    response, err := s.claude.Chat(message, context)
    return response, err
}

What AI Can Access

Can Access Cannot Access
Your documents Other tenants’ documents
Your chat history Other users’ history
Your collections System internals
Your tenant config Database schemas

Tenant Data Model

┌─────────────────────────────────────────────────────────────────┐
│                        Tenant                                    │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ tenant_id: uuid (partition key)                             ││
│  │ subdomain: "acme-corp"                                      ││
│  │ name: "Acme Corporation"                                    ││
│  │ tier: "team"                                                ││
│  │ settings: { ... }                                           ││
│  └─────────────────────────────────────────────────────────────┘│
│                              │                                   │
│              ┌───────────────┼───────────────┐                  │
│              │               │               │                  │
│        ┌─────▼─────┐   ┌─────▼─────┐   ┌─────▼─────┐           │
│        │   Users   │   │ Documents │   │Collections│           │
│        │tenant_id  │   │tenant_id  │   │tenant_id  │           │
│        └───────────┘   └───────────┘   └───────────┘           │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Cross-Tenant Protection

Request Validation

// Cross-tenant access attempt detection
func validateResourceAccess(ctx context.Context, resourceTenantID string) error {
    requestTenantID := ctx.Value("tenant_id").(string)

    if resourceTenantID != requestTenantID {
        // Log security event
        securityLog.Warn("cross_tenant_attempt",
            "request_tenant", requestTenantID,
            "resource_tenant", resourceTenantID,
        )

        // Return generic error (don't leak info)
        return ErrNotFound
    }

    return nil
}

Audit Logging

All access attempts logged:

{
  "event": "document_access",
  "tenant_id": "tenant_123",
  "user_id": "user_456",
  "document_id": "doc_789",
  "action": "read",
  "success": true,
  "timestamp": "2026-01-18T10:30:00Z",
  "ip_address": "192.168.1.1",
  "user_agent": "..."
}

Storage Isolation

Shared Storage (Default)

supabase-bucket/
├── tenant_abc123/
│   └── documents/
│       └── ...
├── tenant_def456/
│   └── documents/
│       └── ...

Dedicated Storage (Team+)

Each Team+ tenant gets isolated bucket:

archivus-tenant-abc123/    ← Dedicated bucket
├── documents/
├── analytics/
└── exports/

BYOB Storage (Enterprise)

Customer-controlled infrastructure:

customer-s3-bucket/        ← Customer's AWS account
├── archivus/
│   └── tenant_abc123/
│       └── ...

Performance Optimization

RLS Performance

Optimized policies for O(1) per-row overhead:

-- Optimized: Uses index efficiently
CREATE POLICY "tenant_isolation" ON documents
    USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- Index supports RLS filtering
CREATE INDEX idx_documents_tenant ON documents(tenant_id);

Query Planning

EXPLAIN ANALYZE SELECT * FROM documents WHERE name LIKE '%contract%';

-- Output shows RLS filter applied first:
-- Index Scan using idx_documents_tenant on documents
--   Index Cond: (tenant_id = '...')
--   Filter: (name ~~ '%contract%'::text)

Compliance Alignment

Standard Multi-Tenant Support
SOC 2 Logical separation, access controls
GDPR Data isolation, right to deletion
HIPAA PHI isolation, audit logging
ISO 27001 Access control, segregation

Verification

Tenant Isolation Test

# Attempt cross-tenant access (should fail)
curl -X GET "https://api.archivus.app/api/v1/documents/doc_from_other_tenant" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Response: 404 Not Found (not 403, to avoid leaking existence)

RLS Verification

-- As tenant_a
SET app.tenant_id = 'tenant_a_uuid';
SELECT COUNT(*) FROM documents;  -- Returns: 150

-- As tenant_b
SET app.tenant_id = 'tenant_b_uuid';
SELECT COUNT(*) FROM documents;  -- Returns: 89

-- No way to access other tenant's data


Questions about security? Contact security@ubiship.com