BXUP Track progress & test yourself — free
Database & ERP · Playbook · Advanced · ~5 min read
DATABASE & ERP · PLAYBOOK · ADVANCED

Make RAG Respect Row-Level Security

A vector index does not know your tenants. If retrieval runs as a superuser and similarity is the only filter, your RAG feature is a cross-tenant leak with good UX. This playbook builds retrieval that inherits row-level security — the caller's identity filters the vectors — then attacks it with adversarial probes until nothing leaks.

Tier: Advanced · Workflow area: Governed Data Access · ~45 minutes

The failure mode is quiet and catastrophic: a user asks the assistant a question, retrieval pulls the nearest neighbors from the whole corpus, and a competitor's contract terms end up paraphrased in the answer. Entitlement enforcement must live in the database, where it already exists as RLS — not in application filters someone will forget, and never in the prompt. The DBA who can prove "retrieval cannot see what the caller cannot see" owns a control every AI feature in the org needs.

When to use

  • Any RAG or semantic-search feature over multi-tenant or entitlement-scoped datacustomer portals, internal tools spanning departments, anything where two users must get different corpora.
  • Before an MCP server exposes vector search (pb-34) — the MCP client's identity must scope retrieval, or the assistant becomes the bypass.
  • Retrofitting a pilot that ran as the app's superuser rolemost pilots do. This playbook is the difference between a pilot and a feature.

When NOT to use

  • Single-tenant, uniformly readable corporapublic docs, an org-wide wiki with no restrictions. RLS adds real query-planning cost; do not pay it for no boundary.
  • When entitlements live only in an external policy engineif the database cannot know the caller's scope (OPA-style central authz, complex ABAC), enforce at that layer instead — but then the leak-test battery here still applies to it.
  • As the only control for column-level secretsRLS filters rows; a permitted row's sensitive columns need column grants or masking (pb-22) on top.

The recipe

  1. Put a tenant key on the vector table and switch RLS on.

    ALTER TABLE doc_chunks
      ADD COLUMN tenant_id int NOT NULL;
    
    ALTER TABLE doc_chunks ENABLE ROW LEVEL SECURITY;
    ALTER TABLE doc_chunks FORCE ROW LEVEL SECURITY;  -- binds owner too
    
    CREATE POLICY tenant_isolation ON doc_chunks
      USING (tenant_id = current_setting('app.tenant_id')::int);
    

    FORCE matters: without it the table owner bypasses every policy — the classic silent hole. The policy reads the caller's tenant from a session setting the application sets per request.

  2. Make the retrieval path carry identity — no privileged detours. The retrieval role is unprivileged and non-owner; every connection sets the tenant before querying:

    CREATE ROLE rag_reader LOGIN PASSWORD '...';
    GRANT SELECT ON doc_chunks TO rag_reader;
    
    -- per request, from the app or MCP layer:
    SET app.tenant_id = '42';
    SELECT chunk_id, content, 1 - (embedding <=> $1) AS score
    FROM doc_chunks
    ORDER BY embedding <=> $1
    LIMIT 8;
    

    The query itself contains no tenant filter — that is the point. RLS injects it, so a forgotten WHERE clause in some future code path cannot leak.

  3. Read the plan: RLS meets ANN in a specific, checkable way. Run EXPLAIN (ANALYZE) on the retrieval query as rag_reader. The HNSW scan returns nearest neighbors first, and the RLS predicate filters them after — so a small tenant may get 2 results back from LIMIT 8. Fixes to evaluate, with measurements: raise hnsw.ef_search so more candidates survive filtering; use pgvector 0.8+ iterative scans; or, for hard tenant walls at scale, partition by tenant so each tenant's index is separate. Ask AI to lay out the trade-offs for your tenant-size distribution, then verify its claims against your EXPLAIN output — this recall-under-RLS interaction is the technical heart of the playbook.

  4. Build the leak-probe battery — ten attacks, written down before you run them. Have AI draft probes, then add your own; the battery must include at minimum: querying with no app.tenant_id set (must error or return zero rows, never default open); a forged tenant id outside the caller's grant (app-layer check must catch); connecting as the table owner without FORCE (regression probe); a semantic query targeting a known other-tenant document verbatim ("summarize the Acme renewal terms"); SQL through any raw-query MCP tool attempting SET app.tenant_id; a chunk whose text contains another tenant's data planted by a bad ingest (this probe tests your pipeline, not RLS); the app's connection pool reusing a session without resetting the setting; retrieval during a tenant migration; a JOIN path around the policy via an unprotected side table; and an EXPLAIN-based oracle probing row counts.

  5. Run the battery as a real client and score it. Execute every probe through the actual application or MCP path, not psql-as-admin. Record per probe: what was attempted, what came back, verdict. An honest first run typically fails 1–2 probes — the connection-pool reset and the unprotected side table are the usual culprits. Sample finding worth framing: probe 7: pool reused session, previous tenant's setting persisted — 8 cross-tenant chunks returned. Fix: SET LOCAL inside per-request transaction. Re-run: 0 rows. SET LOCAL scoped to the transaction is the standard fix — settings die with the transaction, so pool reuse is safe.

  6. Make the battery a regression suite and document the guarantee. Script all ten probes, run them in CI against staging on every change to schema, policies or retrieval code. Publish the guarantee in writing: which tables carry RLS, what identity flows from where, what the probes cover, and the last green run. That document is what you hand the security review — and pb-52's audit procedure will cite it.

Make it yours

  • SQL ServerRow-Level Security via a filter-predicate function on SESSION_CONTEXT(); same probe battery, with special attention to whether elevated app roles bypass the predicate.
  • OracleVPD policies or Real Application Security scope the rows; with 23ai vector search in the same database, the identical retrieval-inherits-policy pattern applies.
  • Dedicated vector storesengines without real RLS push you to namespace-per-tenant plus app-enforced scoping; the probe battery becomes more important there, because the enforcement layer is your code.
  • RAG over ERP dataentitlements are role- and org-level (FLS, authorization objects); retrieval must run in a context mirroring the user's ERP authorizations — vendor copilots claim this, and pb-38's spot-check discipline is how you verify it.

Quality check

  • FORCE ROW LEVEL SECURITY is set and probe-verifiedthe owner-bypass hole is closed, and a probe proves it stays closed.
  • All probes ran through the production pathapp or MCP client, real pooling, real session lifecycle. Admin-console tests validate nothing about the feature.
  • The recall-under-filtering behavior was measured per tenant sizesmallest tenant included. "LIMIT 8 returned 8" for the big tenant says nothing about the small one.
  • The battery reruns automaticallya leak test that ran once is a snapshot; policies, pools and schemas all drift. Green-run history is part of the feature's health.
Warning

The retrieved chunks flow onward into an LLM prompt — retrieval-side enforcement is void if the app logs full prompts to a shared log store, caches answers across users, or lets the model return raw chunks verbatim to a different session. Trace the entitlement boundary all the way to the response, not just to the SELECT.

Key Takeaway:

Entitlement-aware RAG is row-level security doing its oldest job on a new access path — enforced in the engine, carried by identity, and believed only after adversarial probes fail to break it. Ten scripted attacks, rerun forever: that is what "our AI can't leak tenant data" means when you say it in a security review.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in