Fine-Grained Access Control in Enterprise RAG: Pre-Filtering vs. Post-Filtering, Zanzibar ReBAC Models, and Zero-Trust Retrieval Architecture

Deploying Retrieval-Augmented Generation (RAG) across enterprise knowledge repositories introduces a security boundary that simple vector search was never designed to enforce. In corporate environments spanning Google Workspace, Microsoft SharePoint, Notion, Confluence, and internal ticket systems, access permissions are dynamic, hierarchical, and deeply nested. Attempting to enforce security at the prompt generation layer by instructing language models to ignore unauthorized context is fundame

6 min
Fine-Grained Access Control in Enterprise RAG: Pre-Filtering vs. Post-Filtering, Zanzibar ReBAC Models, and Zero-Trust Retrieval Architecture

Deploying Retrieval-Augmented Generation (RAG) across enterprise knowledge repositories introduces a security boundary that simple vector search was never designed to enforce. In corporate environments spanning Google Workspace, Microsoft SharePoint, Notion, Confluence, and internal ticket systems, access permissions are dynamic, hierarchical, and deeply nested.

Attempting to enforce security at the prompt generation layer by instructing language models to ignore unauthorized context is fundamentally broken. Prompt injections, latent attention leakage, and soft context blending can easily bypass conversational instructions. Security in enterprise RAG must operate deterministically at the retrieval and storage tiers: unauthorized document chunks must never enter the model context window.

Implementing fine-grained authorization across vector databases requires balancing retrieval latency, search recall, index fragmentation, and permission synchronization lag.


Access Control Topologies in Vector Retrieval

Enforcing document Access Control Lists (ACLs) in vector search architectures relies on three primary query topologies: metadata pre-filtering, post-filtering authorization, and hybrid two-pass retrieval.

Hybrid Access Control and Zero-Trust Retrieval Architecture

1. Metadata Pre-Filtering (Pushdown Index Filtering)

In metadata pre-filtering, every document chunk stored in the vector database is tagged with access identifiers (such as user IDs, team IDs, or role tags). When a user issues a query, the application extracts the user's identity claims and injects a boolean filter into the Approximate Nearest Neighbor (ANN) query.

# Metadata pre-filter query in a vector store
results = vector_index.query(
    vector=query_embedding,
    top_k=10,
    filter={
        "$and": [
            {"tenant_id": {"$eq": "org_corp_9941"}},
            {"allowed_groups": {"$in": ["engineering", "security_auditors"]}}
        ]
    }
)
  • Strict Security Guarantees: Unauthorized vectors are excluded before distance calculations and ranking occur, preventing unauthorized data from influencing scoring or entering retrieval caches.
  • The Selectivity Cliff in HNSW Graphs: Hierarchical Navigable Small World (HNSW) graphs construct proximity links across the entire vector space. When a metadata filter matches only a tiny fraction of the corpus (low selectivity, such as under 1% of chunks), standard greedy graph traversal hits dead ends, causing recall to collapse to zero unless the engine falls back to expensive brute-force scans.
  • Tuple Explosion and Static Drift: If permissions are granted per user rather than per broad group, every vector payload must store large arrays of allowed identities. Updating permissions requires updating millions of vector payloads across the index.

2. Post-Filtering Authorization (Overfetch and Verify)

Post-filtering decouples vector indexing from access control. The vector database performs standard ANN search across the unpartitioned corpus, over-fetching a wide candidate set (for example, fetching 100 candidate chunks to return the top 10). The retrieval proxy then evaluates the candidate chunk IDs against an external authorization engine (such as OpenFGA or SpiceDB) and strips out inaccessible chunks.

  • Index Integrity: Vector index graph connectivity remains optimal because ANN traversal is unconstrained by metadata masks.
  • Top-K Starvation: If the most semantically relevant documents belong to restricted folders that the requesting user cannot access, all over-fetched candidates may be rejected by the authorization check, returning zero results even if accessible relevant documents exist lower in the corpus.
  • Scoring Leakage: Over-fetching across forbidden documents incurs wasted compute and exposes the system to subtle timing side-channels regarding the existence and density of restricted knowledge domains.

3. Hybrid Two-Pass Retrieval

To mitigate the selectivity cliff and top-K starvation, production architectures deploy a two-pass hybrid approach:

  1. Coarse Pre-Filtering: Apply a high-level static filter at the vector database layer based on broad boundaries (such as tenant ID, organization unit, or classification level). This shrinks the candidate pool without creating graph fragmentation.
  2. ANN Vector Retrieval: Retrieve an over-fetched pool of candidates (typically 3x to 5x of the requested target count).
  3. Fine-Grained Dynamic ReBAC Post-Filtering: Execute a batched permission check against an external authorization service to filter the surviving candidates down to the final authorized set.

Relationship-Based Access Control (ReBAC) with Zanzibar Models

Enterprise document permissions are rarely flat lists of user IDs. Instead, they follow hierarchical graphs: a user inherits access to a document because they belong to an engineering team, which is assigned to a project folder, which contains a workspace document.

Baking these dynamic relationships into static vector metadata causes severe operational friction whenever an employee changes roles or a folder is shared with a new group. Modern enterprise RAG systems integrate Relationship-Based Access Control (ReBAC) inspired by the Google Zanzibar paper.

// OpenFGA Schema for Enterprise Document Chunks
model
  schema 1.1

type user

type group
  relations
    define member: [user]

type folder
  relations
    define viewer: [user, group#member] or viewer from parent
    define parent: [folder]

type document
  relations
    define parent_folder: [folder]
    define direct_viewer: [user, group#member]
    define viewer: direct_viewer or viewer from parent_folder

type chunk
  relations
    define parent_doc: [document]
    define can_read: viewer from parent_doc

Authorization Query Patterns: CheckPermission vs. LookupResources

When integrating Zanzibar engines like SpiceDB or OpenFGA into RAG pipelines, developers choose between two API operations:

  • LookupResources (Pre-Filter Input): Resolves all document IDs a given user has permission to read. While this produces an exact list for vector pre-filtering, executing LookupResources across enterprise corpora with millions of objects introduces high latency (often over 150ms) and yields filter payloads too large for vector query engines.
  • CheckBulkPermissions (Post-Filter Step): Evaluates a candidate list of 50 chunk IDs in a single RPC call against the user identity. Because evaluation runs concurrently over a bounded candidate list, p95 latency remains under 10ms.
import grpc
from authzed.api.v1 import (
    Client,
    CheckBulkPermissionsRequest,
    CheckBulkPermissionsRequestItem,
    ObjectReference,
    SubjectReference
)

def filter_authorized_chunks(client: Client, user_id: str, candidate_chunks: list[dict]) -> list[dict]:
    items = [
        CheckBulkPermissionsRequestItem(
            resource=ObjectReference(object_type="document_chunk", object_id=chunk["id"]),
            permission="can_read",
            subject=SubjectReference(object=ObjectReference(object_type="user", object_id=user_id))
        )
        for chunk in candidate_chunks
    ]
    
    response = client.CheckBulkPermissions(CheckBulkPermissionsRequest(items=items))
    
    authorized_ids = {
        item.pair.resource.object_id
        for item in response.pairs
        if item.item.permissionship == 1  # PERMISSIONSHIP_HAS_PERMISSION
    }
    
    return [c for c in candidate_chunks if c["id"] in authorized_ids]

Native Row-Level Security (RLS) in Relational Vector Stores

For architectures utilizing relational database extensions such as PostgreSQL with pgvector, access control can be pushed down to native PostgreSQL Row-Level Security.

In this architecture, application connections set session-level claims before issuing vector distance calculations:

-- Enable Row Level Security on the chunk embeddings table
ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY;

-- Define RLS policy joining dynamic user memberships
CREATE POLICY user_chunk_access_policy ON document_chunks
FOR SELECT
USING (
    tenant_id = current_setting('app.current_tenant_id')::uuid
    AND (
        EXISTS (
            SELECT 1 FROM document_permissions dp
            WHERE dp.document_id = document_chunks.document_id
              AND dp.subject_id = current_setting('app.current_user_id')::uuid
              AND dp.permission_type = 'read'
        )
        OR EXISTS (
            SELECT 1 FROM group_memberships gm
            JOIN document_group_permissions dgp ON dgp.group_id = gm.group_id
            WHERE gm.user_id = current_setting('app.current_user_id')::uuid
              AND dgp.document_id = document_chunks.document_id
        )
    )
);

Before executing the similarity search, the database session is scoped to the user context:

BEGIN;
SET LOCAL app.current_tenant_id = 'c7e8a931-1052-4a0b-8d5f-14930d6bf450';
SET LOCAL app.current_user_id = 'e2b3c109-8431-4122-bc08-5928f01b4431';

SELECT id, content, 1 - (embedding <=> $1) AS similarity
FROM document_chunks
ORDER BY embedding <=> $1
LIMIT 10;
COMMIT;

RLS policies execute on candidate rows before returning them to the caller. With HNSW and IVFFlat indexes in PostgreSQL, complex subquery joins within RLS policies can prevent the query planner from effectively using the index, forcing index scans followed by iterative filtering that increases query latency compared to unconstrained vector queries.


Managing ACL Drift and Synchronization Lag

One of the most critical vulnerabilities in enterprise RAG systems is permission drift: a document's permissions are revoked in Google Drive or SharePoint, but the cached vector chunks remain accessible in the vector database.

To eliminate authorization drift without requiring full re-indexing of embeddings:

  1. Decouple Embedding Storage from Authorization State: Vector database payloads should only store the immutable object identifier (document ID, chunk ID) and high-level static boundaries (tenant ID). Never store flat lists of mutable user IDs in vector metadata.
  2. Change Data Capture (CDC) Event Pipelines: Connect enterprise source systems to Kafka or Debezium event buses. When a permission change occurs, publish the relationship delta directly to the ReBAC authorization service (SpiceDB or OpenFGA).
  3. Point-in-Time Token Checking: When an authorization check runs during retrieval, pass the source transaction timestamp (or Zanzibar zedtoken) to guarantee consistency between the authorization graph and the search request.

Architecture Trade-Offs

  • Metadata Pre-Filtering: Delivers low latency (under 5ms pushdown) and strict exclusion guarantees, but suffers from severe recall collapse at low selectivity and requires index updates when ACL tags change.
  • Post-Filtering (Pure): Preserves full vector graph connectivity and allows complex ReBAC evaluations, but incurs latency overhead and risks top-K starvation if top matches are restricted.
  • Hybrid Two-Pass: Combines coarse tenant-level pre-filtering with fine-grained ReBAC post-filtering, balancing recall stability with 10-25ms p95 latency.
  • Postgres Native RLS: Simplifies operational topology by embedding authorization logic directly into relational SQL transactions, with 15-40ms query execution overhead depending on index join complexity.

Implementation Guidelines

  1. Never delegate authorization to system prompts. Treat the LLM strictly as a text synthesis engine; only provide context chunks that have passed deterministic authorization checks.
  2. Use coarse-grained partition tags in vector metadata. Filter on tenant boundaries, environments, and immutable classification levels at the ANN search level to preserve index graph traversal performance.
  3. Enforce fine-grained permissions via centralized ReBAC. Use Zanzibar-based authorization engines to evaluate relationship graphs dynamically over the over-fetched retrieval set.
  4. Log every retrieved chunk alongside user context. Record the specific user identity, query text, chunk identifiers, and authorization evaluation tokens to ensure audit compliance across regulated environments.

Sources

Written by

More to read

  • Distributed Vector Search and Sharding Architecture in Production: Horizontal Partitioning, Scatter-Gather Tail Latency, Filter-Aware Routing, and Dynamic Rebalancing

    Scaling vector search beyond tens of millions of high-dimensional embeddings inevitably hits a physical boundary: the single-node memory wall. Because graph-based approximate nearest neighbor (ANN) algorithms such as Hierarchical Navigable Small World (HNSW) require random memory access patterns across graph vertices and high-dimensional vectors, keeping embeddings and index structures in RAM is critical for sub-20ms query latencies. A collection of 100 million 1,536-dimensional float32 vectors

    1 min
  • Modern Hopfield Networks: How Continuous Energy Landscapes Explain Transformer Attention and Exponential Memory

    When Vaswani et al. introduced the Transformer architecture in 2017, scaled dot-product self-attention was presented primarily as a pragmatic computational mechanism: an efficient, highly parallelizable alternative to recurrence and convolutions. By computing pairwise inner products between queries and keys, normalizing via softmax, and taking a weighted sum of values, attention allowed models to route information dynamically across arbitrarily distant tokens. For several years, self-attention

    1 min
  • Inside Ulanqab: How Inner Mongolia Became the 12.5GW Epicenter of China's AI Data Center Boom

    Located approximately 350 kilometers northwest of Beijing, the grassland municipality of Ulanqab in Inner Mongolia has transformed into China's primary hub for artificial intelligence compute infrastructure. Historically recognized for agriculture and mineral extraction, the city now hosts nearly 100 enterprise data centers operating or under active construction, with technology firms pledging an aggregate capacity of 12.5 gigawatts (GW). According to a research note published by Goldman Sachs,

    1 min