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:

  1. **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.
  2. **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.
  3. **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

  1. **`tenant_encryption_key_id`:** The exact identifier of the encryption key that wraps the tenant's vectors, binding each vector to its owning tenant.
  2. **`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.
  3. **`query_authorization_receipt`:** The cryptographic proof that the querying agent is authorized for the specific tenant, preventing filter tampering.
  4. **`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

  1. **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.
  2. **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.
  3. **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.
  4. **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

  1. **Cryptographic Isolation:** Guarantees that even a compromised query engine cannot leak another tenant's data, satisfying multi-tenant SaaS security requirements.
  2. **Filter Bypass Prevention:** Requires authorization receipts to be cryptographically bound to the tenant, preventing filter tampering from causing leaks.
  3. **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
        )