Use this retrieval access matrix

Create two near-identical documents in different tenants and make the forbidden one the best text match. Query as the lower-privilege user. The forbidden chunk, title, and citation must stay out of retrieval and output.

Retrieval access matrix
ItemCheck or ownerEvidence
User A and public docAllowedCite visible source
User A and user B docDeniedNo chunk or title leak
Revoked user and cached chunkDeniedPurge or recheck
Service admin and draft docRole basedLog access

Test the boundary

Revoke a user and repeat through caches, summaries, and old conversation context. Keep document IDs and permission snapshots with results. An output filter cannot repair a secret that was already placed in the model context.

Worked synthetic case

Synthetic case: Tenant A and Tenant B each have a policy called “Account limits.” Tenant B’s version contains a unique sentence. The search index ranks B’s version higher for a question from A.

Query as Tenant A and inspect retrieved chunk IDs before the model runs. The pass condition is that B’s chunk, title, and citation never enter context or final output. Then revoke A’s access to its own policy and repeat through cache and conversation summary.

Filter by tenant and document permission during retrieval, then recheck at use time for cached results. Also tag chunks at ingestion. A final answer filter is cheap but cannot undo private text already sent to a model provider.

The stricter filter may lower recall when metadata is incomplete. Treat missing permission metadata as denied and repair the index. Do not trade a tenant leak for a slightly better answer.

Handle a stale permission snapshot

Create a document that a user can read, then remove the user from the group while a conversation remains open. Ask a follow-up that requires the old chunk. The retriever should recheck current permission before returning the chunk; a cached summary that already contains the content must also be withheld or cleared. Keep an authorization decision ID with the test trace, but do not store the private text in a public report. Repair missing tenant tags at ingestion and invalidate affected cache entries. Re-run both allowed and denied queries so the fix does not hide all documents. OWASP treats retrieved content as an untrusted boundary; final text filtering cannot undo earlier exposure to a model.

Choose the time of the access decision

Synthetic timeline: worker A may read DOC-A at 11:59. At noon, the permission service changes its access version from 7 to 8 and removes A. Pause one request after vector search but before its chunks enter model context. Resume it after noon. Under a current-permission policy, the app rechecks version 8 and withholds DOC-A. A request finished before noon is a different case; revocation cannot erase text the user already saw.

Test the same timeline for a cached answer, citation preview, saved conversation summary, and export file. Cache keys need user and permission context, or a fresh access gate before use. Deleting one vector row does not remove every derived copy. Record exactly which stores can be invalidated and which are retained under a separate rule.

Prove that missing metadata denies access

Create DOC-B with the unique phrase “blue receipt 701” and remove its tenant tag in the test index. Ask as A for that phrase. The retriever must deny the unclassified chunk rather than assume it is public. Now repair the tag, grant A access, and show a positive retrieval result. This checks safe defaults without hiding the whole corpus.

For permission changes in flight, log source document ID, chunk IDs, access version, decision time, and model dispatch time. Define an allowed propagation window before testing; zero-delay revocation cannot be inferred from an eventual cache purge. If a chunk was already sent to a provider, record that exposure and follow the deletion process instead of calling a later clean answer a reversal. OWASP’s RAG guide covers chunk access and derived copies.

Primary source