OWASP LLM08 (Vector and Embedding Weaknesses) is the quieter half of RAG security. Embeddings are not one-way hashes — they can be partially inverted to recover sensitive input, and a poorly-partitioned vector store leaks one tenant's or one user's data into another's retrieval results.
Tier: Advanced · Workflow area: RAG & agent security
This playbook hardens your own vector layer: strict tenant isolation, access-controlled retrieval, and testing for cross-tenant leakage and embedding-inversion exposure, so the store can't become a side channel around your app's authorization.
Threat, briefly. Two failure modes. Cross-tenant leakage: tenant B's query retrieves tenant A's chunks because the index isn't partitioned or filters are bypassable. Embedding inversion: an attacker with access to stored vectors reconstructs enough of the original sensitive text to matter. Both route around your app-layer authorization.
Partition by tenant at the index level, not the query level. Use separate namespaces/collections or enforced metadata filters that the query layer cannot omit. A missing filter must fail closed, not return everything.
# namespace per tenant; a query with no tenant is rejected upstream
if not tenant_id:
raise PermissionError("no tenant scope")
hits = index.query(vector=q, namespace=tenant_id, top_k=8)
Propagate row/document ACLs into retrieval. Store the source document's access controls as vector metadata and filter on the current user's entitlements, so retrieval can never surface a chunk the user couldn't read at the source (this pairs with the sensitive-disclosure playbook).
hits = index.query(vector=q, namespace=tenant_id,
filter={"acl_group": {"$in": user.groups}}, top_k=8)
Treat stored vectors as sensitive data. Encrypt at rest, restrict who and what can read the raw vectors, and avoid exposing nearest-neighbor endpoints that let an attacker probe the space freely — that probing is the setup for inversion and membership inference.
Minimize what you embed. Redact PII before embedding where the task allows (pb-30). A vector you never created can't be inverted; a redacted input limits what inversion could recover.
Rate-limit and monitor retrieval. Bulk or systematic nearest-neighbor queries are the signature of an inversion or extraction attempt. Cap query volume per identity and alert on anomalies.
Test isolation and inversion exposure on your local store. Seed tenant A with a canary document, then, as tenant B, run queries engineered to surface it. Separately, attempt to recover canary text from raw vectors.
# local store you own
python seed_tenant.py A --canary "PROJECT-BLUEJAY-secret"
python query_as.py B "bluejay" # must return zero tenant-A chunks
Embeddings are lossy but not private. A vector store is a data store — give it the same isolation, encryption, and access control you'd give the database the source text came from.
The defense holds when tenant B cannot reach tenant A's canary and raw-vector access reveals little. Success criteria:
Automate the cross-tenant canary check as a CI gate — it is the single most important regression to hold, since one dropped filter reopens the whole index.
Vector-store security is often the finding clients haven't considered, because they think of the index as "just math." Run the isolation and inversion tests inside the client's environment against a staging index seeded with synthetic canaries — never their real embedded corpus copied out. The controls (namespaces, ACL propagation, encryption, query rate limits) are all native to the vector DB the client already runs, so nothing leaves their boundary. Deliver the cross-tenant canary test as a standing CI check and, for DPDP-scoped data, document that the vector store enforces the same access controls and minimization as the source system — evidence that personal data didn't lose its protections by being embedded.