Your warehouse enforces row- and column-level security. Then a RAG pipeline copies that same data into a vector store, and every access rule evaporates: anyone who can query the index can retrieve any chunk.
Tier: Advanced · Workflow area: Docs, lineage & governance
Securing RAG against leakage is named in security postings for a reason — and the data side of it is a data engineering job. This playbook designs metadata so every vector-store entry carries ACL tags, filters retrieval by the caller's entitlements, and — the part most teams skip — adversarially tests that the filter actually holds.
If the corpus is genuinely uniform-access (public docs, one team's own runbooks), skip the machinery — but write down that decision, because corpora grow.
Make the source of truth explicit. Decide where entitlements live — warehouse grants, an IdP group mapping, a document-system ACL — and treat the vector store as a downstream consumer of it. The index never gets its own hand-maintained permission model; it inherits one.
Design ACL metadata at chunk level. Every chunk record carries the tags needed to answer "may this caller see this?" at query time: e.g. {"source_doc": ..., "acl_groups": ["finance-fp&a"], "sensitivity": "internal", "tenant_id": ...}. Derive tags in the ingestion pipeline from the source ACL — never let humans (or an LLM) assign them ad hoc. A chunk with no resolvable ACL gets quarantined, not indexed with a default-open tag.
Enforce with the store's native mechanism. Filtering belongs in the database, not in application code that remembers to add a WHERE clause:
pgvector + row-level security — the strongest teaching pattern, because the database enforces it even if the app forgets:
ALTER TABLE rag_chunks ENABLE ROW LEVEL SECURITY;
CREATE POLICY chunk_acl ON rag_chunks
USING (acl_group = ANY (string_to_array(current_setting('app.caller_groups'), ',')));
The API layer sets app.caller_groups from the verified identity per connection; similarity search then physically cannot return out-of-scope rows.
Qdrant / Weaviate / Milvus — mandatory payload/metadata filter injected server-side or by a thin gateway the caller cannot bypass.
Pinecone — namespaces for hard tenant isolation plus metadata filters for group-level ACLs within a namespace.
OpenSearch — document-level security (DLS) mapped from the caller's roles.
Propagate changes, especially revocations. Access is dynamic: a user leaves finance, a document is reclassified. Wire your CDC/refresh pipeline (the same one that handles content updates) to re-derive ACL tags on change, and measure revocation lag — the time between a source-system revoke and the index no longer serving that chunk to that caller. Agree an SLA for it; hours is usually acceptable, days is a finding.
Adversarially test for leakage. This is the step that separates "designed" from "secured". Build a leakage test suite that runs in CI and after every index rebuild: for each persona, a set of queries specifically crafted to pull content they must not see (ask about the restricted content by topic, by paraphrase, by asking the assistant to summarize "everything about X"). Assert zero restricted chunks in the retrieval results — check the retrieval layer's returned chunk IDs, not just the generated answer, because generation can leak from a retrieved-but-unquoted chunk. An LLM is useful here as the red-team query generator: give it the restricted docs' titles/topics and have it produce paraphrase attacks.
Log retrieval with identity. Every query logs caller identity, filter applied, and chunk IDs returned. Without this you cannot audit an incident, and with it you can continuously mine for near-misses.
Test retrieval, not answers. A leakage suite must assert on the chunk IDs the store returned per persona — the generated text merely hides which restricted content entered the context window.
In client engagements this workflow usually is the deliverable of a RAG security review. Run everything inside the client's environment — the restricted corpus and the leakage-test transcripts are both sensitive. If an external LLM is used only to generate red-team queries from document titles/topics, confirm that even titles are shareable; otherwise generate attacks with a self-hosted model or by hand. Deliver the leakage-test suite itself (runnable, in their CI) plus the revocation-lag measurement — a client who can re-run the proof after every index rebuild has received security, not a report about security.