Skip to content
allsrc.dev
Go back

Pattern 7: RAG Access Control and Provenance

RAG Access Control and Provenance is an architectural pattern that enforces strict authorization checks during the retrieval phase and attaches source citations to generated answers, ensuring users only receive information they are permitted to see. Filter candidate documents by authorization before ranking or synthesis, and carry citation metadata into every generated answer.

Understanding RAG Access Control

Most RAG implementations ask one question at retrieval time: what documents are relevant to this query? The question they do not ask is: is this user or this agent allowed to see those documents?

The retrieval system does not know the difference between a query from an internal analyst and a query from a customer-facing agent unless the harness tells it. If the index includes documents with different classification levels, sensitivity categories, lifecycle states, or audience restrictions, retrieval can silently bypass access control. Vector similarity is not authorization — a semantically relevant document can still be the wrong document to expose. The gap appears when access checks happen before indexing, after answer generation, or only in the application UI. The RAG pipeline itself needs authorization as part of retrieval.

Implementing RAG Access Control and Provenance

Filter candidate documents by authorization before ranking or answer synthesis, and return source identifiers, lifecycle state, and citation metadata with the answer.

flowchart TD
  A[RetrievalRequest] --> B[Load candidate documents]
  B --> C{Tenant match?}
  C -->|no| X[Exclude]
  C -->|yes| D{Domain match?}
  D -->|no| X
  D -->|yes| E{Lifecycle active?}
  E -->|no| X
  E -->|yes| F{ACL intersects user roles?}
  F -->|no| X
  F -->|yes| G[Score and rank]
  G --> H[Synthesize answer with citations]

In LangGraph, the retrieve node filters candidate documents through the RetrievalAuthorizer before anything reaches the model, then routes on whether any authorized source survived — generate synthesizes a cited answer, no_answer returns a grounded refusal rather than hallucinating from content the user was not entitled to see:

def retrieve(state: AgentState) -> dict:
    candidates = [
        RetrievedDocument("1", "Order consumer runbook", "public", "uri1", {}),
        RetrievedDocument("2", "Signing keys", "private", "uri2", {}),
    ]
    allowed = [tracker.sign_document(d) for d in authorizer.filter_documents(candidates)]
    return {"authorized": allowed}

def route_retrieval(state) -> Literal["generate", "no_answer"]:
    return "generate" if state["authorized"] else "no_answer"

builder.add_edge(START, "retrieve")
builder.add_conditional_edges("retrieve", route_retrieval,
    {"generate": "generate", "no_answer": "no_answer"})

Considerations for Secure RAG Retrieval

Consider the following factors when you implement this pattern:

References



Previous Post
Pattern 6: Prompt Injection and Goal Hijacking
Next Post
Pattern 8: Memory Isolation