# RAG isolation: ingestion, retrieval, citations and revocation

Trace document authorization through vector search, generated answers, citation downloads, caches and sharing changes.

## Investigation sequence

1. Seed private canaries
2. Verify direct access
3. Trace retrieval
4. Change ownership state
5. Compare exposed results

## Used in these playbooks

[AI, agents & MCP](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/ai.html) · [Web & API](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/web-api.html) · [OSS & self-hosted](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/oss.html)

## Setup and security invariant

Use owned tenants A/B with private and shared collections. Place unique markers only in each tenant's private document. Record ingestion state, collection/document IDs and permissions. The invariant is: **the effective caller can retrieve or resolve only currently authorized content through every supported path**. Reuse the [authorization matrix](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/modules/authz-matrix.html) for object swaps and [identity/session checks](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/modules/identity-session.html) for tenant identity.

Unauthorized chunks may reach the model without appearing in its answer; filtered search can still return an unprotected citation download. OWASP identifies risks from shared vector stores and inadequate access controls. Test those boundaries separately. [OWASP Vector and Embedding Weaknesses](https://genai.owasp.org/llmrisk/llm082025-vector-and-embedding-weaknesses/)

## Controlled procedure

1. **Prove readiness.** As A retrieve A's canary; as B retrieve B's. Then request B's document directly as A and verify denial. Record whether permissions belong to tenants, teams, individual users or externally synchronized groups. A canary missing from its owner's search is an ingestion problem, not isolation evidence.
2. **Compare retrieval paths.** As A, issue the same semantic query used by B, with no secret marker in the query. Exercise search, chat, document lookup and citation download independently. In a local white-box fixture, log the authenticated principal, effective query filter, retrieved chunk IDs and resolver decision. Repeat with an absent tenant filter and a B collection ID only where the API actually accepts those fields.
3. **Inspect ingestion ownership.** Upload a harmless A document while supplying B's collection or source reference. Confirm where the completed document is stored and who can read it. A rejected upload is a useful negative control; a successful job response alone does not show cross-tenant insertion. Follow async workers and document reindexing through [source dataflow](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/modules/source-dataflow.html).
4. **Test sharing transitions.** Share one synthetic document, retrieve it legitimately, then revoke access. After the product's documented synchronization interval, test a fresh conversation, an existing conversation, direct download, citations and cached answers. Record which content was already delivered before revocation; users retaining their earlier copy is not a new disclosure.
5. **Separate caches from filters.** Warm a cache as B, repeat the same query as A, then repeat after an allowed lab cache clear or with a new synthetic document. Compare cold/warm results and cache keys. If only the warm path leaks, preserve that sequence. Test an A-only cache warm as the benign comparison.

## Reusable case matrix

| Case | Caller and state | Expected observation |
|---|---|---|
| Owner baseline | B → B-private document | Retrieval and resolver succeed |
| Tenant isolation | A → B-private document | No unauthorized chunk or download |
| Shared baseline | A → explicitly shared document | Allowed content is available |
| Revocation | A after share removal and sync | New protected retrieval is denied |
| Cache separation | B warms; A repeats | B's private result is absent |

## Local command cheat sheet

```bash
python3 -I -S - <<'PY'
import secrets
for owner in ('A', 'B'):
    print(owner + '_PRIVATE_' + secrets.token_hex(16))
PY
rg -n 'tenant_id|collection_id|document_id|cache_key|namespace' ./src
python3 -I -S -m json.tool lab/retrieval-case.json
```

Store each generated marker only in its private fixture. Record caller, path, expected/actual document IDs, visibility and timestamp. These commands do not call hosted models or vector databases. [Python secrets](https://docs.python.org/3/library/secrets.html)

## Evidence and checklist

Report the strongest observed boundary precisely: unauthorized chunk retrieval in logs, user-visible disclosure, or unprotected download. Do not present an internal trace as proof that an external attacker can see its contents. Exclude intentional shared collections, stale permissions within the stated synchronization window and markers included in the user's own query.

- [ ] Both owners can retrieve their own seeded documents.
- [ ] Search, answers and citation resolution were compared separately.
- [ ] Cold/warm cache and sharing-state controls are recorded.
- [ ] Actual caller identity and document ownership support the claim.
- [ ] Synthetic documents and temporary sharing grants were removed.

## Beyond document retrieval

Run [AI state lifecycle](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/modules/ai-state-lifecycle.html) when a private chunk is copied into a conversation, checkpoint, durable memory, background job or protocol cache. Record the new object owner and retention rule at each copy. A fresh search filter cannot repair an earlier copy served through an unprotected history/export API. Reuse the revocation controls above and distinguish content already delivered under a valid grant from a newly exposed private artifact.


## Vulnerability reverse references

- [RAG retrieval or citation resolver crosses tenant boundaries](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/vulnerabilities/rag-cross-tenant-retrieval.html) — Search filters, chunk retrieval or citation download omit an ownership decision required for the effective caller.
- [RAG cache or fresh retrieval ignores revoked access](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/vulnerabilities/rag-cache-revocation-leak.html) — A new retrieval or cache response exposes protected content after its applicable permission revocation has taken effect.
- [Agent conversation, checkpoint or memory crosses ownership boundaries](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/vulnerabilities/ai-persistent-state-ownership.html) — Persisted agent state or its resume/export path is accessible or mutable by a principal outside its intended owner or sharing grant.
- [Private MCP result is reused across authorization contexts](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/vulnerabilities/mcp-private-cache-context.html) — A shared cache delivers caller-specific protocol data outside the authorization context that produced it.
