One Missing Predicate Away from a Breach: Multi-Tenant Safety for AI Over Shared Data
Isolation and policy patterns to keep multi-tenant AI data systems secure and stable.
In a shared data platform serving multiple tenants through AI agents, the distance between a working system and a cross-tenant data exposure incident is often one missing predicate. A generated query that omits a tenant filter, a retrieval pipeline that surfaces another tenant metadata, or a cached response served to the wrong session can each independently cause a trust-breaking event.
Single-identity access controls address what one user can see. Multi-tenant safety addresses a harder problem: ensuring that the probabilistic, session-driven behavior of AI agents cannot leak data, context, or performance impact across tenant boundaries in shared infrastructure.
Five threat categories, all triggered in real systems
Five risk categories define the multi-tenant threat surface, and each one has been triggered in production systems I have seen or reviewed.
Accidental cross-tenant data access from missing predicates is the most common and the most preventable.
Inference leakage through aggregates allows deduction of another tenant's data from small-cell outputs.
Noisy-neighbor performance impact occurs when one tenant's agent workload degrades service quality for others.
Policy bypass through prompt manipulation attempts to override tenant scoping through conversational context.
Stale entitlement propagation means cached or delayed entitlement updates continue granting access after organizational changes.
A robust design assumes each of these will eventually be triggered, intentionally or accidentally. Defense depth across multiple independent enforcement points is the only reliable mitigation.
Tenant identity must be immutable, period
Tenant identity must be system-assigned, not user-supplied or model-inferred from natural language. Derive tenant context from the authenticated identity and session, attach it as immutable metadata to the request trace, and enforce it at every downstream layer. Never allow the model or user prompt to specify or override tenant scope.
This is the most critical pattern. If tenant context can be altered within the generation path, every subsequent control is compromised. The signed request context should include tenant identity as an immutable claim.
Filter at every layer, assume each one can fail
Use multiple independent enforcement points so that any single layer can fail without causing cross-tenant exposure. The policy gateway checks tenant entitlement. The SQL validator requires tenant predicates in all generated queries. The database enforces row-level security at the engine level. The result filter verifies output tenant consistency before returning data. Each layer operates independently, and the combination provides defense-in-depth.
Here is what the database-layer enforcement looks like concretely. This is SQL Server row-level security enforcing tenant isolation:
-- Security predicate function
CREATE FUNCTION security.fn_tenant_filter(@tenant_id INT)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
SELECT 1 AS result
WHERE @tenant_id = CAST(SESSION_CONTEXT(N'tenant_id') AS INT);
GO
-- Apply to fact tables
CREATE SECURITY POLICY security.TenantIsolationPolicy
ADD FILTER PREDICATE security.fn_tenant_filter(tenant_id)
ON dbo.fact_revenue,
ADD FILTER PREDICATE security.fn_tenant_filter(tenant_id)
ON dbo.fact_transactions,
ADD FILTER PREDICATE security.fn_tenant_filter(tenant_id)
ON dbo.dim_customer
WITH (STATE = ON);
Before executing any agent-generated query, the execution layer sets SESSION_CONTEXT from the authenticated tenant claim:
EXEC sp_set_session_context @key = N'tenant_id', @value = @authenticated_tenant_id;
This means even if the application-layer validator has a bug and a query reaches the database without a WHERE tenant_id = ... predicate, RLS silently filters rows to the authenticated tenant. The agent never sees another tenant's data, regardless of what SQL it generates.
The retrieval layer leaks too, not just SQL
Retrieval pipelines must be tenant-scoped where metadata itself is sensitive. A global retrieval index that surfaces tenant-specific table patterns, custom dimensions, or sensitive naming conventions creates an information disclosure vector even if SQL execution is properly scoped.
This is a frequently overlooked risk. If the LLM prompt context or few-shot examples for one tenant include schema patterns, sample queries, or metric definitions derived from another tenant environment, cross-tenant information leaks at the context layer regardless of execution-layer controls. Use tenant-scoped retrieval indexes, tenant-tagged metadata partitions, and tenant-isolated prompt template caches. The grounding architecture should treat tenant awareness as a first-class concern.
Aggregates can identify individuals across tenants
Cross-tenant leakage can occur through small-cell aggregates even when row-level access is properly enforced. A query returning "3 employees in Department X with salary above $200K" may effectively identify individuals.
Mitigations include minimum cohort thresholds for sensitive domains, suppression for low-count groups, differential-privacy-inspired noise for specific reporting classes where acceptable, and detection of repeated-query differencing patterns across sequential requests. These controls complement the inference risk mitigations for single-identity access but apply specifically to the cross-tenant dimension where the stakes are higher.
One tenant's workload should never degrade another's
Tenant safety extends beyond data access to quality-of-service guarantees. Use tenant-aware resource governance with per-tenant quotas and burst policies, workload shaping by risk tier, and circuit breakers for runaway or abusive query patterns. This prevents one tenant from degrading platform reliability for others. The workload isolation patterns described earlier in this series for latency budgets and physical design provide the foundation, while tenant-level governance adds the policy layer.
Version every policy decision and log why it fired
When requests are blocked or transformed, the system should return clear policy reasoning without exposing internal security details. Version policy rules and log the policy version applied, rule IDs triggered, and decision outcome for every request. This enables reproducibility and audit defensibility. When incidents occur, versioned policy logs allow precise reconstruction of what rules were in effect and whether enforcement behaved correctly.
Test isolation explicitly, not just correctness
Dedicated test suites for multi-tenant safety should run on model, prompt, policy, and schema changes. Include missing-tenant-predicate scenarios, cross-tenant join attempts, adversarial prompt patterns designed to override tenant context, and stale entitlement cache cases.
Here is the validation code that runs before every query reaches the database. It uses AST parsing to verify tenant predicates are present:
import sqlglot
def validate_tenant_isolation(sql: str, tenant_tables: list[str], tenant_column: str = "tenant_id") -> dict:
"""Verify that all tenant-scoped tables have a tenant predicate."""
tree = sqlglot.parse_one(sql)
referenced_tables = {t.name for t in tree.find_all(sqlglot.exp.Table)}
# Which tenant-scoped tables does this query touch?
scoped_tables = referenced_tables & set(tenant_tables)
if not scoped_tables:
return {"valid": True, "reason": "no tenant-scoped tables"}
# Check WHERE clause for tenant predicates
where = tree.find(sqlglot.exp.Where)
if where is None:
return {
"valid": False,
"reason": f"no WHERE clause but touches: {scoped_tables}"
}
predicate_columns = {col.name for col in where.find_all(sqlglot.exp.Column)}
if tenant_column not in predicate_columns:
return {
"valid": False,
"reason": f"missing {tenant_column} predicate for: {scoped_tables}"
}
return {"valid": True}
# Usage
TENANT_TABLES = ["fact_revenue", "fact_transactions", "dim_customer"]
result = validate_tenant_isolation(
"SELECT region, SUM(amount) FROM fact_revenue GROUP BY region",
TENANT_TABLES
)
# result: {"valid": False, "reason": "missing tenant_id predicate for: {'fact_revenue'}"}
This is the application-layer check. It runs before RLS, so it catches missing predicates proactively rather than relying on the database to silently filter. Both layers should exist.
Prepare for containment before you need it
Prepare for fast containment: kill-switches for high-risk query classes, emergency policy override paths, tenant-targeted audit replay capability, and predefined communication templates for security stakeholders. Operational readiness is part of safety architecture, not a separate process.
The way this plays out in production is instructive. A generated query accidentally omits tenant predicates. SQL validation flags the violation and rejects the query. Even if the validator had a bug, database-level RLS blocks cross-tenant rows from being returned. Now consider the same query running under a shared service account without RLS enforcement, where the application-layer tenant filter has a caching bug. Data from three tenants is returned in a single response. Defense depth is not redundancy. It is an acknowledgment that every individual layer will eventually fail.
The checklist before you go live
Before certifying multi-tenant AI data infrastructure:
Tenant context is system-assigned and immutable. The model and user prompt cannot alter it.
At least three independent enforcement layers validate tenant scope: policy gateway, SQL AST validator, and database RLS.
Retrieval pipelines and LLM context are tenant-scoped. No cross-tenant metadata in prompts or few-shot examples.
Aggregate release controls enforce minimum cohort thresholds for sensitive domains.
Per-tenant resource quotas and circuit breakers are enforced and tested under load.
If any layer is absent, the system is operating on the assumption that other layers will never fail. In shared enterprise infrastructure, that assumption is a liability.

