Enterprise Tenant Isolation
Multi-tenant data isolation for MSSP and enterprise deployments with zero breaking changes
Overview
QuickSecure supports enterprise multi-tenant isolation at the database, API, and ML governance layers. Each tenant's data is isolated while sharing the global threat intelligence and model infrastructure.
Resolution
X-Tenant-Idheader- License-based auto-resolution
- Middleware injection
- Legacy compatibility (null = global)
Data Layer
- EF Core global query filters
SaveChangesInterceptor- Auto-inject TenantId on write
- Cross-tenant write prevention
ML Layer
- Per-tenant inference metrics
- Per-tenant drift detection
- Hybrid model registry
- Tenant training eligibility
Tenant Resolution
The TenantResolutionMiddleware resolves tenant context on every request using a priority chain:
| Priority | Source | Description |
|---|---|---|
| 1 | X-Tenant-Id header | Explicit tenant ID from the agent or API client |
| 2 | License lookup | If the API key maps to a license with a TenantId, use it |
| 3 | Null (global) | Legacy endpoints without tenant context. Fully supported. |
How It Works
# Agent heartbeat with explicit tenant
curl -X POST https://corxor.com/api/telemetry/heartbeat \
-H "X-Api-Key: YOUR_API_KEY" \
-H "X-Tenant-Id: tenant-abc" \
-H "Content-Type: application/json" \
-d '{"machineId":"MACHINE-001","machineName":"WORKSTATION-1"}'
# Legacy endpoint (no tenant header) — still works, uses global context
curl -X POST https://corxor.com/api/telemetry/heartbeat \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"machineId":"MACHINE-002"}'
X-Tenant-Id continue to work. Their data lives in the global partition (TenantId = null).Data Safety
Two EF Core mechanisms enforce tenant isolation at the database level:
1. Global Query Filters
Every query on tenant-aware entities automatically includes a WHERE TenantId = @currentTenant filter. This is applied by EF Core at the provider level — application code cannot bypass it.
2. SaveChanges Interceptor
The TenantSaveChangesInterceptor runs on every SaveChangesAsync call:
- New entities: TenantId is auto-injected from the current request context
- Modified entities: If TenantId is being changed, the write is blocked and an error is logged
- Null context: If no tenant is resolved, the entity is written with
TenantId = null(global)
TenantViolationException. This is audit-logged as a security event.Tenant-Aware Entities
| Entity | TenantId Column | Notes |
|---|---|---|
| TelemetryEventV4 | string? | All telemetry events |
| ModelVersion | string? | null = global, non-null = tenant-dedicated |
| DriftRecord | string? | Per-tenant drift snapshots |
| InferenceMetric | string? | Per-tenant confusion matrix |
| ThreatEvent | string? | Per-tenant threat events |
| AutonomousDecision | string? | Per-tenant AI Judge decisions |
Legacy Data Backfill
Existing data created before tenant isolation can be retroactively tagged using the TenantBackfillWorker.
How Backfill Works
- Runs once at application startup
- Finds ML rows where
TenantId IS NULLbut theEndpointIdmaps to a License with a TenantId - Updates TenantId via the chain:
EndpointId→Endpoint.LicenseId→License.TenantId - Processes in batches of 1,000 rows to avoid long-running transactions
Admin Visibility
The admin ML dashboard supports tenant-scoped queries via the tenantId query parameter:
# Global ML status (all tenants combined)
GET /api/telemetry/admin/ml/status
# Tenant-specific ML status
GET /api/telemetry/admin/ml/status?tenantId=tenant-abc
# Tenant-specific model versions
GET /api/telemetry/admin/ml/models?tenantId=tenant-abc
# Tenant-specific drift history
GET /api/telemetry/admin/ml/drift?tenantId=tenant-abc