Document-Level Access Control Lists for AI Retrieval Systems
TL;DR — Document-level ACLs enforce per-document permissions in RAG systems, ensuring users only retrieve documents they are authorized to access. Two approaches: pre-filter (query permission system before vector search) and post-filter (check permissions on results). pgvector supports RLS for database-level enforcement. Metadata filtering in vector stores (Pinecone, Bedrock) enables attribute-based access control. Systems must fail closed — denying access when permissions cannot be verified. Without document-level ACLs, RAG retrieval exposes all indexed documents to every user.
The vector database usually lacks the complex Access Control Lists found in systems like SharePoint, Google Drive, or Salesforce. When you index documents from these sources into a RAG pipeline, the permissions that existed in the source system disappear. A vector search returns the most semantically similar chunks regardless of who owns them or who has access (Quellix Labs 2026).
This creates a critical security gap. An engineer asking the AI about "salary bands" could retrieve HR documents they have no permission to see. A sales rep asking about "Q3 financials" could access confidential board reports. The AI happily generates responses using documents the user should never see.
Document-level ACLs close this gap by enforcing per-document permissions at the retrieval layer. This post covers the architecture, implementation patterns, and failure modes. For tenant isolation at the platform level, see our companion post.
The RAG Permission Problem
When documents are ingested into a vector store for RAG, they are typically:
1. Chunked into smaller passages
2. Embedded into vectors
3. Stored with metadata (source, title, date)
What is often missing: the access control metadata from the source system. SharePoint documents have permissions. Google Drive files have sharing settings. Salesforce records have role-based visibility. None of this survives the ingestion pipeline unless you explicitly capture and enforce it.
SharePoint, Drive, CRM] -->|has permissions| ACLs[Document ACLs] Source -->|ingestion pipeline| Chunk[Chunking] Chunk --> Embed[Embedding] Embed --> Vector[Vector Store] Vector -->|missing ACLs| Query[User Query] Query -->|returns ALL matches| LLM[LLM] LLM -->|exposes unauthorized data| Response[Response] ACLs -.->|must be carried through| Vector
The fix: capture ACLs at ingestion time, store them as metadata in the vector store, and enforce them at query time.
Two Approaches to ACL Enforcement
Pre-filter: Query permissions before vector search
The permission system is queried first to get the list of document IDs the user can access. Only those documents are searched in the vector store.
| Aspect | Pre-filter |
|---|---|
| How it works | Get authorized document IDs → search only those in vector store |
| Efficiency | Good for large corpora with low hit rates |
| Latency | Permission lookup adds overhead before search |
| Tools | SpiceDB LookupResources, Cerbos Query Plan |
Post-filter: Check permissions after vector search
The vector store returns the top-K results. Each result's document ID is checked against the permission system. Only authorized results are passed to the LLM.
| Aspect | Post-filter |
|---|---|
| How it works | Vector search → check permissions on each result |
| Efficiency | Good for high hit rates (most results are authorized) |
| Latency | Permission checks add overhead after search |
| Risk | May return zero authorized results if top-K are all unauthorized |
| Tools | SpiceDB CheckPermission, Cerbos resource checks |
When to use which: If you have a large corpus and users typically have access to a small subset, pre-filter is more efficient. If most users have access to most documents, post-filter is faster (Pinecone 2026).
Implementation With pgvector and Row-Level Security
Since pgvector runs on PostgreSQL, you can use Row Level Security (RLS) to enforce document permissions at the database level. This is the strongest enforcement model — the database itself rejects unauthorized rows.
| Step | What it does |
|---|---|
| 1. Store ACLs | Each document chunk has an owner_id or allowed_groups field |
| 2. Create RLS policy | Database policy filters rows based on current user identity |
| 3. Set session context | Application sets app.current_user_id per request |
| 4. Vector search respects RLS | Similarity search automatically filters to authorized rows |
With RLS, every SELECT query on the vector table — including similarity searches — implicitly filters to documents the current user can access. The application cannot bypass this even with a bug (Supabase 2026).
Implementation With Metadata Filtering
Vector databases that support metadata filtering (Pinecone, AWS Bedrock Knowledge Bases, Weaviate) allow you to attach permission attributes to each embedding and filter at query time.
| Vector store | Filter mechanism | Enforcement level |
|---|---|---|
| pgvector | Row Level Security (RLS) | Database-level (strongest) |
| Pinecone | Metadata filter on query | Application-level |
| AWS Bedrock | Metadata filter via RetrieveAndGenerate API | Application-level |
| Weaviate | Where filter with operator conditions | Application-level |
| Chroma | Where filter with metadata conditions | Application-level |
The critical difference: database-level enforcement (pgvector RLS) cannot be bypassed by application bugs. Application-level enforcement (metadata filtering) depends on the application correctly constructing the filter every time. A single missing filter in one code path exposes all documents.
External Policy Decision Points
For complex permission models, use an external Policy Decision Point (PDP) rather than embedding permission logic in your application:
| PDP | How it works | Integration |
|---|---|---|
| Cerbos | Policy DSL defines resource permissions | Query Plan generates vector store filters |
| SpiceDB | Google Zanzibar-inspired relationship-based ACLs | LookupResources or CheckPermission API |
| AWS Verified Permissions | Cedar policy language | Converts policy decisions to metadata filters |
| OPA (Open Policy Agent) | Rego policy rules | External evaluation, filter construction in app |
The PDP evaluates the user's permissions and returns a filter expression that the application applies to the vector store query. This separates permission logic from application code and ensures consistent enforcement (Cerbos 2026).
Fail-Open vs Fail-Closed
The most dangerous failure mode in ACL-enforced RAG is fail-open: when the permission check fails or times out, the system returns all documents instead of none.
| Failure mode | What happens | Risk |
|---|---|---|
| Fail-open | Permission check fails → return all results | Critical: exposes all documents |
| Fail-closed | Permission check fails → return no results | Safe: user sees nothing, reports issue |
Always fail closed. If the PDP is unreachable, if the metadata filter cannot be constructed, or if the permission system times out, deny access. A user who sees nothing will report a bug. A user who sees everything may not.
ACL Synchronization
Permissions change. Users gain and lose access. Documents are reclassified. The ACL enforcement system must stay synchronized with the source system:
| Sync strategy | How it works | Tradeoff |
|---|---|---|
| Real-time sync | Webhook from source system updates vector store metadata | Most accurate, complex to implement |
| Scheduled sync | Batch job reads source ACLs and updates vector store | Simpler, risk of stale permissions |
| On-demand check | Query source system at retrieval time | Always current, adds latency |
| Hybrid | Scheduled sync + on-demand for high-value documents | Balanced approach |
For PII redaction pipelines, ACL metadata should be captured at ingestion time alongside the redaction process, ensuring both privacy and access controls are enforced from the moment data enters the system.
FAQ
What are document-level ACLs in AI retrieval systems?
Document-level ACLs (Access Control Lists) enforce per-document permissions in RAG systems. They ensure that users can only retrieve and use documents they are authorized to access, preventing unauthorized data exposure through AI-generated responses.
How do you implement access control in a vector database?
Two main approaches: pre-filter (query the permission system for authorized document IDs before searching the vector store) and post-filter (search the vector store, then check permissions on each result). Pre-filter is efficient for large corpora with low hit rates; post-filter works well for high hit rates.
Does pgvector support row-level security for RAG?
Yes. Since pgvector runs on PostgreSQL, you can use Row Level Security (RLS) policies to restrict which document chunks are returned during vector similarity search. RLS policies implicitly filter results based on the current user's identity.
What is metadata filtering in vector databases?
Metadata filtering attaches attributes (department, user role, sensitivity level) to vector embeddings. At query time, the vector search applies these filters so only documents matching the user's permissions are retrieved. AWS Bedrock Knowledge Bases and Pinecone both support this.
What happens if ACL filtering fails open in a RAG system?
If the middleware that constructs the metadata filter fails to enforce permissions, documents from all users or tenants are exposed in the retrieval results. This is a critical security failure. The system must fail closed — denying access when the filter cannot be verified.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →