Skip to main content
Version: Local Β· In Progress

Deployment Models

ThreatWeaver supports two deployment models with fundamentally different database architectures. The application behavior is controlled by the DEPLOYMENT_MODE environment variable.

Overview​


Model 1: Dedicated (On-Premises)​

When to use: Single customer deployment β€” on-premises, private cloud, or dedicated managed instance.

Environment variable: DEPLOYMENT_MODE=dedicated

Database Architecture​

AspectDetail
SchemaAll tables in public schema
Tenant isolationNot applicable β€” single tenant
Data locationCustomer's own infrastructure
Migrationsnpm run migrate:local (synchronize mode)
Tenant slugSet via DEDICATED_TENANT_SLUG env var (e.g., acme-corp)

How It Works​

  • All queries run against the public schema
  • No schema switching overhead
  • The DEDICATED_TENANT_SLUG is used for branding and configuration (e.g., displaying the company name) but does NOT create a separate schema
  • Simplest deployment β€” standard PostgreSQL, no multi-tenant complexity

Setup​

# .env for on-premises deployment
DEPLOYMENT_MODE=dedicated
DEDICATED_TENANT_SLUG=acme-corp
DATABASE_URL=postgresql://user:pass@localhost:5432/threatweaver

# Run migrations (synchronize mode β€” creates/updates all tables in public schema)
npm run migrate:local

Model 2: SaaS (Multi-Tenant)​

When to use: Hosting multiple customers on a single ThreatWeaver instance β€” the SaaS offering.

Environment variable: DEPLOYMENT_MODE=saas

Database Architecture​

AspectDetail
Schema strategySchema-per-tenant (tenant_acme, tenant_globex, etc.)
public schema roleSeed templates, default data, tenant registry, new schema creation
Tenant isolationComplete β€” each tenant's data in its own PostgreSQL schema
Migrationsnpm run migrate:dev (migration-based) or npm run migrate:production (transaction-safe)
Tenant creationAPI endpoint provisions new schema from public template

How It Works​

Schema Isolation Details​

Every tenant gets their own schema with a complete copy of all tables (133 entities). The SchemaManager class handles:

  1. Schema creation: CREATE SCHEMA IF NOT EXISTS "tenant_acme" + DDL from template
  2. Request scoping: SET search_path TO "tenant_acme", public β€” all unqualified table queries resolve to tenant schema
  3. RLS (Row-Level Security): app.current_tenant_id GUC set at session level for defense-in-depth
  4. Connection pool safety: Schema and tenant GUC reset before returning connections to the pool β€” prevents context bleed between tenants
  5. Validation: Schema names validated with regex /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/ to prevent SQL injection

What Lives in public Schema (SaaS mode)​

PurposeTables/Data
Tenant registryTenant metadata, subscription status, entitlements
DDL templatesTable creation SQL used to seed new tenant schemas
Default dataDefault scanner agents (50 agents), default scan templates, compliance frameworks
Shared configurationPlatform-level settings, AI provider configs
Migration trackingTypeORM migration history

New Tenant Provisioning Flow​

Setup​

# .env for SaaS deployment
DEPLOYMENT_MODE=saas
DATABASE_URL=postgresql://user:pass@supabase-host:5432/threatweaver

# Run migration-based migrations (not synchronize)
npm run migrate:dev # for development/staging
npm run migrate:production # for production (transaction-safe)

Comparison Table​

FeatureDedicated (On-Premises)SaaS (Multi-Tenant)
DEPLOYMENT_MODEdedicatedsaas
Customers per instance1Unlimited
Database schemaSingle publicSchema-per-tenant
Data isolationPhysical (separate DB)Logical (separate schema)
Migration strategysynchronize modeMigration files
public schema usageAll application dataTemplates + tenant registry
Tenant slug sourceDEDICATED_TENANT_SLUG env varJWT tenantSlug claim
Search pathAlways publictenant_{slug}, public
RLS policiesNot neededActive (defense-in-depth)
Connection poolStandardPer-request schema switching with reset
InfrastructureCustomer-managedBluCypher-managed (Render + Supabase)
Cost modelLicense feePer-tenant subscription

Key Source Files​

FilePurpose
backend/src/multi-tenant/schema-manager.tsSchema creation, QueryRunner scoping, pool safety
backend/src/multi-tenant/tenant-local-storage.tsAsyncLocalStorage-based tenant context
backend/src/multi-tenant/rls-setup.tsRow-Level Security policy configuration
backend/src/middleware/auth.tsJWT extraction of tenantId/tenantSlug
backend/scripts/migrate-local.tsDedicated mode migrations (synchronize)
backend/scripts/migrate-dev.tsSaaS mode migrations (migration-based)
backend/scripts/migrate-production.tsProduction migrations (transaction-safe)

Security Considerations​

  • Schema name injection: All schema names validated against strict regex before use in SET search_path
  • Connection pool bleed: QueryRunner resets search_path and app.current_tenant_id before release
  • Cross-tenant access: RLS policies enforce app.current_tenant_id matches row-level tenant markers
  • Schema drop protection: dropTenantSchema() refuses to drop anything not prefixed with tenant_
  • Audit trail: All tenant provisioning and schema operations logged to SecurityAuditLog

This page documents the actual implementation in backend/src/multi-tenant/. Last verified against source code.