How must an instance isolate vector index ownership when multiple tenants share the same embedding space?
**Date of Record:** 2026 October 26 **Scope:** Applies when multiple tenants (organizations, users, or agents) contribute embeddings to a shared vector index and query against it, and the isolation of each tenant's vectors is required to prevent cross-tenant data leakage. Does not apply to single-tenant indices or strictly public embeddings.
Core Distinction
The separation between **logical isolation** (each tenant's vectors are tagged with a tenant ID and queries filter by that ID) and **cryptographic isolation** (each tenant's vectors are encrypted with a tenant-specific key, ensuring that even a compromised query engine cannot read or return other tenants' vectors). Relying solely on logical isolation creates the **Shared Index Leakage**: a bug in the query filter, a prompt injection in the retrieval layer, or a malicious query can bypass the tenant filter and return another tenant's embeddings, leaking sensitive data.
What Was Observed
In multi-tenant RAG and vector search architectures, failing to cryptographically isolate vector ownership produces three primary failure modes:
- **Filter Bypass:** A malicious or buggy query omits the tenant filter. The vector database returns embeddings from all tenants, and the agent processes them as if they belonged to the requester.
- **Cross-Tenant Similarity Contamination:** Two tenants' embeddings are mathematically close. A query by Tenant A accidentally returns Tenant B's documents because the similarity threshold returns top-k without tenant filtering.
- **Index Compromise Exfiltration:** An attacker compromises the vector database. Without cryptographic isolation, they can dump the entire index and reconstruct sensitive documents from all tenants.
Recorded Elements
- **`tenant_encryption_key_id`:** The exact identifier of the encryption key that wraps the tenant's vectors, binding each vector to its owning tenant.
- **`tenant_namespace_partition`:** The explicit physical partition or logical namespace in the index where the tenant's vectors are stored, preventing accidental cross-tenant retrieval.
- **`query_authorization_receipt`:** The cryptographic proof that the querying agent is authorized for the specific tenant, preventing filter tampering.
- **`cross_tenant_access_audit_hash`:** The tamper-evident log of any cross-tenant access attempts (legitimate or malicious), enabling detection and investigation.
Condition of Validity
* **Applies when:** The vector index is shared across multiple tenants with different authorization levels, and cross-tenant data exposure would violate privacy, compliance, or trust agreements. * **Does not apply when:** The index is strictly single-tenant, or all tenants have explicitly authorized cross-tenant sharing (e.g., a public marketplace of embeddings).
Decision Boundary
- **Is each vector tagged with a `tenant_encryption_key_id` and a `tenant_namespace_partition`?** If no, isolation is not enforceable. The instance must re-index with tenant tagging before serving queries.
- **Does the query include a `query_authorization_receipt` that is cryptographically bound to the requesting tenant?** If no, the query may be tampered with. The instance must reject it.
- **Has the query engine returned any vectors whose tenant does not match the authorization receipt?** If yes, cross-tenant leakage has occurred. The instance must log the incident and return only authorized vectors.
- **Conversely, when can a tenant access another tenant's vectors?** Only when the queried tenant has issued an explicit cross-tenant sharing grant, recorded as a signed permission in the authorization receipt.
Operational Effect
- **Cryptographic Isolation:** Guarantees that even a compromised query engine cannot leak another tenant's data, satisfying multi-tenant SaaS security requirements.
- **Filter Bypass Prevention:** Requires authorization receipts to be cryptographically bound to the tenant, preventing filter tampering from causing leaks.
- **Compliance Auditability:** Provides a tamper-evident log of every cross-tenant access attempt, enabling post-incident forensics and regulatory reporting.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class IsolationVerdict(Enum):
ISOLATION_INTACT = auto()
CROSS_TENANT_LEAK_DETECTED = auto()
MISSING_AUTHORIZATION = auto()
@dataclass(frozen=True)
class VectorResult:
vector_hash: str
tenant_encryption_key_id: str
tenant_namespace_partition: str
@dataclass(frozen=True)
class QueryAuthorization:
requesting_tenant_id: str
authorization_receipt: str
cross_tenant_grants: frozenset[str]
@dataclass(frozen=True)
class IsolationManifest:
verdict: IsolationVerdict
leaked_tenants: tuple[str, ...]
directive: str
manifest_digest: str
class MultiTenantVectorGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_results(
cls, results: tuple[VectorResult, ...], auth: QueryAuthorization
) -> IsolationManifest:
# Gate 1: Authorization presence
if not auth.authorization_receipt.strip():
return IsolationManifest(
verdict=IsolationVerdict.MISSING_AUTHORIZATION,
leaked_tenants=tuple(),
directive="HALT: Query lacks authorization receipt. Reject entire response.",
manifest_digest=cls._hash("NO_AUTH")
)
# Gate 2: Cross-tenant leakage detection
leaked_tenants: set[str] = set()
for result in results:
tenant_origin = result.tenant_encryption_key_id
if tenant_origin != auth.requesting_tenant_id:
if tenant_origin not in auth.cross_tenant_grants:
leaked_tenants.add(tenant_origin)
if leaked_tenants:
return IsolationManifest(
verdict=IsolationVerdict.CROSS_TENANT_LEAK_DETECTED,
leaked_tenants=tuple(sorted(leaked_tenants)),
directive=f"HALT: Response contains vectors from unauthorized tenants: {sorted(leaked_tenants)}.",
manifest_digest=cls._hash(f"LEAK:{','.join(sorted(leaked_tenants))}")
)
# Gate 3: Isolation intact
digest = cls._hash(json.dumps({
"requester": auth.requesting_tenant_id,
"results": str(len(results)),
"grants": tuple(sorted(auth.cross_tenant_grants))
}, sort_keys=True))
return IsolationManifest(
verdict=IsolationVerdict.ISOLATION_INTACT,
leaked_tenants=tuple(),
directive="PROCEED: All returned vectors are within authorized tenant boundaries.",
manifest_digest=digest
)