09. Document Access Control
Enterprise RAG requires access control at the document level. Not all users should see all documents. Financial analysts should not access HR files. External contractors should not see proprietary research. Auditors should see everything except draft documents.
Document-level access control filters search results based on user permissions. Every document has an access control list (ACL) defining who can read it. Every query applies ACL filtering to results.
# ACL-based document filtering
def retrieve_with_access_control(
query: str,
user: User,
top_k: int = 10
) -> list[RetrievalResult]:
# Phase 1: Vector search (returns candidates without access checks)
candidates = vector_db.search(query, k=top_k * 10) # Over-fetch for filtering
# Phase 2: Access control filtering
authorized = []
for doc in candidates:
if access_control.has_permission(user, doc.document_id, 'read'):
authorized.append(doc)
if len(authorized) >= top_k:
break
return authorized
The over-fetch strategy (retrieve 10x candidates, filter down) is necessary because ACL filtering removes results. A strict k=10 query might return only 3 authorized documents.
Permission models vary. Role-based access control (RBAC) assigns permissions based on job function. Attribute-based access control (ABAC) evaluates permissions based on document attributes (department, classification, project). Discretionary access control (DAC) lets document owners grant permissions.
# Example ABAC policy
policy:
- id: "finance-reports-access"
effect: "allow"
subjects:
roles: ["analyst", "manager", "executive"]
resources:
document_types: ["quarterly_report", "annual_report", "forecast"]
departments: ["finance", "executive"]
conditions:
- attribute: "classification"
operator: "in"
value: ["internal", "confidential"]
- attribute: "document_status"
operator: "equals"
value: "published"
Performance implications are severe. ACL filtering requires checking each result against the permission store. With 10,000 candidate vectors and 50ms per permission check, filtering alone takes 500 seconds.
Optimizations include: permission caching (cache user permissions for 5 minutes), pre-filtering (query only authorized partitions), and permission-denormalization (embed permissions in vector metadata for fast rejection).
The audit trail is non-negotiable for compliance. Every retrieval event must log: who queried, what they retrieved, when, and the access control decision. This enables security investigations when documents leak.
Failure modes include stale permissions (user fired yesterday still has access for 4 hours), permission escalation bugs (admin role accidentally gets access to all documents), and search leakage (unauthorized documents appear in context but not in results—still a leak).
Design an access control system for a law firm where attorneys can see documents for cases they are assigned to, paralegals can see all documents for cases in their practice group, and clients can only see documents their attorney explicitly shared.