Security
Tenant-Scoped Vector Indexes Are Not Tenant Isolation
AI copilots are built with a stack of isolation controls: tenant-scoped vector indexes, RBAC, namespace separation, encryption boundaries. Each control does what it claims. None of them isolate tenants.
The standard AI copilot isolation stack
The pattern is consistent across production AI systems. A company building a copilot for 500 enterprise customers reaches for the same five primitives.
Tenant-scoped vector indexes. Each tenant's embeddings are tagged with a tenant identifier, and queries filter by that identifier before returning matches.
RBAC and ABAC. Role and attribute-based access control gates which users can read which documents, enforced at the API layer.
Namespace isolation. Each tenant gets a string prefix on their keys, paths, or identifiers. Pinecone has namespaces, Redis has key prefixes, S3 has bucket prefixes.
Isolated memory layers. Conversation history and agent memory are tagged with a session and tenant, so retrieval scopes to the right context.
Encryption boundaries. Each tenant's data is encrypted at rest, often with a tenant-specific key managed in a KMS.
The architecture diagram looks defensible. The audit checklist is satisfied. The compliance team signs off.
The shared substrate
Every control on the list is enforced in the same process that serves the request. The vector index lives in one database. The namespace is a string that the application code prepends. The RBAC check is a function call before the query runs. The encryption key is unwrapped in memory before the row is read.
The data is co-mingled. The enforcement is logical. The boundary is a WHERE tenant_id = clause, a string comparison, or a function return value.
This is the same pattern as row-level security, applied to a different stack. The mechanism varies. The shape does not.
The retrieval bug
The failure mode is well understood. A change to the retrieval path drops the tenant filter, applies it incorrectly, or queries the wrong namespace. The vector search returns nearest neighbors across the full index. The bug is silent. There is no exception, no log entry, no alert.
The query succeeds. The model gets ten chunks. The user sees a coherent answer. The chunks belonged to four different tenants.
This pattern is not hypothetical. It is the AI copilot equivalent of a missing WHERE clause, and it appears in the same places: a refactor, a new code path, a fallback handler that bypasses the standard query builder, a debugging endpoint left in production.
What "tenant-scoped" actually means
The phrase "tenant-scoped vector index" usually means one of three things, ordered by isolation strength.
A tenant_id field on every vector, filtered at query time. The vectors live in one index, share one set of HNSW graphs, and compete for the same memory. A bug in the filter returns cross-tenant results.
A namespace per tenant within a single index. Stronger than field filtering because the namespace is part of the query plan, not a predicate. The vectors still share the same physical storage and the same process. A bug that selects the wrong namespace returns the wrong tenant's data.
A separate index per tenant. The vectors live in different storage, different processes, and often different machines. A bug in the application code cannot return data from an index it did not connect to.
Most production systems use the first two patterns. The third is dismissed as expensive and operationally complex, in the same way that database-per-tenant was dismissed five years ago.
The bug radius
The shared property of every logical control is that they all live inside the bug radius. When the retrieval code has a defect, the controls that should prevent cross-tenant exposure are running in the same process as the bug.
RBAC does not save you when the query is wrong. The function returned True. The user is authorized. The query is the problem.
Namespaces do not save you when the namespace string is computed incorrectly. The retrieval code constructed the wrong key. The store returned exactly what was asked for.
Encryption does not save you when the wrong row is decrypted. The KMS handed back the right key. The application asked for the wrong row.
Five controls, one process, one bug. They fail together because they were never independent.
What physical isolation looks like for AI workloads
Physical isolation moves the boundary outside the application process. Each tenant gets their own database, their own vector index, their own cache. The connection string is different. The data files are different. The backups are different.
When a retrieval bug drops the tenant filter, the query still cannot return another tenant's data. The connection is bound to one tenant's database. The vector index does not contain other tenants' embeddings. The row is not in this database to leak.
The architecture is not new. It is what early SaaS companies did before query-time isolation became the default pattern. The reason it fell out of favor is operational. Managing a thousand databases used to require a thousand provisioning scripts, a thousand backup schedules, a thousand migration runs.
That part is solved. Per-tenant database orchestration is a tooling problem, and the tooling exists.
When logical controls still make sense
There are scenarios where logical isolation is the right tradeoff. Internal applications with a small, fixed set of tenants. Systems where the data is not sensitive enough to justify the per-tenant operational cost. Early-stage products where the architecture has not stabilized and shipping speed is the priority.
Logical controls are also a reasonable intermediate step. RBAC is strictly better than no access control. Tenant-scoped vector indexes are strictly better than a single shared index with no filtering. The point is not that these controls are useless. The point is that they cannot be the only line of defense for an AI copilot serving enterprise customers.
When the customer list includes regulated industries, when the data includes confidential documents and conversation history, when one cross-tenant leak destroys the contract, the isolation stack needs a layer outside the bug radius.
Moving past query-time enforcement
The fundamental limitation of every logical isolation control is the same. The data is shared, and the enforcement must work perfectly on every code path to maintain the boundary.
Physical isolation removes the need for the enforcement layer entirely. The boundary is structural. There is no filter to forget, no namespace to mistype, no policy to evaluate. The retrieval bug still exists. The bug cannot read data that is not in the database it connected to.
For AI copilots specifically, this means each tenant's source documents, embeddings, and conversation memory belong behind the same isolation boundary. Splitting them across systems with different isolation models reintroduces the bug radius problem at the system boundaries.
TenantsDB
TenantsDB implements database-per-tenant isolation across PostgreSQL, MySQL, MongoDB, and Redis. Every tenant gets their own database, their own credentials, and their own connection string. Queries are routed through a proxy that resolves the tenant and connects to the correct database. AI copilot workloads that store embeddings in PostgreSQL with pgvector, conversation history in MongoDB, and session caches in Redis get the same physical boundary across all three.
The application never needs to set a session variable, include a tenant filter, or hope the namespace string was computed correctly. The bug radius shrinks to a single tenant's database, because that is the only database the connection can reach.
Start free with up to 5 tenants at docs.tenantsdb.com.