# How Should Enterprises Enforce Row-Level Security in RAG Pipelines in 2026?

opensilo.co · September 23, 2026

> What Row-Level Security Means for a RAG Pipeline Row-level security for a RAG pipeline means that every chunk returned to a model is filtered by the...

## What Row-Level Security Means for a RAG Pipeline

Row-level security for a RAG pipeline means that every chunk returned to a model is filtered by the caller's entitlements at retrieval time, using an identity the server derives from a verified token rather than from anything the client can edit. In a multi-tenant B2B system the minimum policy is tenant_id equal to the caller's tenant, but the practical policy also covers group membership, document-level ACLs, sensitivity labels, and document status such as draft, archived, or embargoed. The enforcement point should sit below the model, inside the database or vector store, so an application bug cannot simply return another tenant's rows. As of September 2026, the defensible default is database-native row-level security (RLS) in PostgreSQL when the corpus lives in Postgres with pgvector, or vector-native metadata filters with a mandatory tenant key when it does not.

**Also worth reading:** [How Do Enterprises Implement Secure Cross Domain Data Pipelines Without Breaking Trust Boundaries?](https://opensilo.co/knowledge/how_do_enterprises_implement_secure_cross_domain_data_pipelines_without_breaking_trust_boundaries.php) · [How Can Modern Enterprises Maintain Absolute Security While Executing B2B Data Un-siloing Strategies?](https://opensilo.co/knowledge/how_can_modern_enterprises_maintain_absolute_security_while_executing_b2b_data_un-siloing_strategies.php) · [What is post-quantum federated learning security and how do enterprises protect decentralized AI training against quantum decryption?](https://opensilo.co/knowledge/what_is_post-quantum_federated_learning_security_and_how_do_enterprises_protect_decentralized_ai_training_against_quantum_decryption.php)

That is only the first half of the answer. The second half is identity propagation: the same tenant, user, and group claims must flow from the identity provider through the API gateway, the application, and the query engine without being re-specified by hand at each hop. If the pipeline cannot prove who is asking, RLS is theatre, because the policy has nothing trustworthy to compare against. RLS is also not a substitute for encryption, data-loss prevention, or output redaction; it governs which rows are eligible for retrieval, not what a model will say once it has read an authorized row.

Treated that way, RLS turns RAG from a search-everything-and-hope design into a permission-aware retrieval system in which the top-k results are computed within the caller's authorized set. The cost is extra engineering and a modest recall penalty when ACLs are very restrictive, but the alternative, a single cross-tenant disclosure, is usually an existential enterprise event. The rest of this answer covers how to build it, how the options compare, where the common failures sit, and what the timeline and budget look like.

## Why Permission Checks Fail in Ordinary RAG Pipelines

Most RAG pipelines do not leak because an attacker asked politely; they leak because of a default path that was never designed with authorization in mind. The classic pattern embeds a query, runs a top-k search across the entire vector index, reranks, and only then applies a tenant filter. By the time the filter runs, the candidate set already contains other tenants' content, and a botched filter, a logging statement, or a model that quotes context verbatim can expose it. Worse, a post-filter applied after top-k quietly destroys recall for the caller, because their own authorized documents were crowded out of the candidate set by rows the filter later removed.

A second failure mode is trusting the application. Code such as WHERE tenant_id = $1 is only as safe as every caller, every connection, and every job that touches the table. Analytics scripts, admin endpoints, migrations, support tooling, and service accounts routinely run with broader privileges and skip the filter. In Supabase, for example, the documentation is explicit that the service_role key bypasses RLS entirely, which is convenient for administration and dangerous if it reaches a user-facing code path. In PostgreSQL, a superuser or a role with BYPASSRLS can read every row regardless of policy, and a table owner escapes RLS unless FORCE ROW LEVEL SECURITY is enabled.

The third failure mode is permission drift. A user belongs to 6 groups on Monday and 9 by Friday, a contractor loses access, a document moves from internal to restricted, and the vector index, the relational ACL tables, and the cache now disagree. RLS cannot help if the underlying entitlement data is stale or if the retrieval layer caches a user's result and serves it to a colleague. Authorization in a RAG pipeline is therefore an ongoing operating commitment, not a one-time migration, and it has to be treated with the same seriousness as network policy or database credentials.

## A Reference Architecture That Holds Up

A workable design starts at the identity provider. An OIDC or SAML login produces a token carrying stable claims: tenant_id, user_id, group memberships, a clearance level, and a policy version. The API gateway verifies the signature, expiry, and audience, then establishes a database session in which the tenant is set from the verified token, for example with SET LOCAL app.tenant_id, never from a request body or query parameter. In PostgreSQL, a policy of the form USING (tenant_id = current_setting('app.tenant_id')::uuid) on the documents, chunks, and embeddings tables means that every subsequent SELECT, including joins, updates, and deletes, is constrained by the session's identity. The same pattern extends to ACL join tables so that group-based rules are evaluated in the database rather than reconstructed in application code.

Embedding storage is the part teams get wrong most often. If vectors live in pgvector as a column on the chunks table, ordinary PostgreSQL RLS applies to them automatically, because the vector is just another column of a protected row. If vectors live in a dedicated engine such as Pinecone, Qdrant, or Weaviate, then the engine's metadata filter becomes the enforcement point, and the metadata must include tenant_id, group ACLs, and lifecycle status as first-class fields rather than free-text tags. Many teams run both, with Postgres holding the authoritative ACLs and a change-data-capture stream projecting entitlements into the vector store; in that pattern, the projection is a security boundary, and it needs its own reconciliation job and lag monitoring.

Everything downstream inherits the policy. Caching keys must include tenant, user or group hash, and policy version, because a cached answer built under last week's permissions is a stale-permission leak. Audit logs should record the principal, the policy version, the document IDs returned, and the filter selectivity, without logging the sensitive text itself. Oracle Database offers a parallel pattern through Virtual Private Database policies and Oracle Label Security, which adds label-based row access on protected tables alongside ordinary database policies; enterprises already standardized on Oracle can use those mechanisms rather than bolting an authorization layer onto the vector query path. The principle is the same everywhere: the model must never see a row the database would refuse to return.

## Comparing Enforcement Points

| Feature | PostgreSQL RLS with pgvector | Vector-native metadata filters | Application-layer pre-filter | Post-generation filtering | Separate index or database per tenant |
| --- | --- | --- | --- | --- | --- |
| Enforcement location | Database engine, below the app | Vector engine, below the app | Request handler | After the model has read context | Physical isolation |
| Protects against app bugs | Yes | Yes, if the engine enforces it | No | No | Yes |
| Main weakness | Requires Postgres for the corpus | Metadata becomes a security-critical store | Every code path must remember the filter | Leaks during retrieval, not just in output | Cost and operational overhead scale with tenants |
| Typical fit | Postgres-native stacks, 10 to 5,000 tenants | Pinecone, Qdrant, Weaviate deployments | Prototypes, single-tenant tools | Low-risk internal demos, never production isolation | Regulated customers, few large tenants |
| Recall impact | None beyond filter selectivity | None beyond filter selectivity | None if correct | High, because candidates are discarded late | None |

The table is a decision aid, not a ranking. PostgreSQL RLS is the strongest single choice when the data already lives in Postgres, because one policy engine protects the relational rows, the ACL joins, and the pgvector columns together, and because a missing filter fails closed by returning nothing. Vector-native filters are the right choice when the corpus is large, distributed, or sharded across engines, but they are only as trustworthy as the metadata pipeline that keeps tenant_id and ACLs current. Application-layer pre-filtering is acceptable for prototypes and single-tenant deployments, and it becomes a liability the moment a second tenant or an admin tool appears. Post-generation filtering is a compliance illusion: it is the last line, and it runs after the unauthorized text has already entered the prompt and the logs.
Separate databases or indexes per tenant deserve a second look. Physical isolation removes cross-tenant queries by construction, which auditors like, and it can simplify tenant-scoped backup and deletion. The trade-off is linear cost: at roughly $70 to $500 per month for a managed vector service entry tier, 50 tenants can mean 50 services or 50 clusters, plus migrations, and a shared Postgres instance with RLS can serve the same tenants for a fraction of that. A reasonable 2026 heuristic is fewer than 10 tenants with strict contractual isolation on dedicated infrastructure, 10 to 5,000 tenants on shared tables with RLS, and a hybrid above 5,000 tenants where regulated customers get dedicated storage and everyone else shares.

## A Practical Implementation Plan

Start with an entitlement inventory. For a typical enterprise, that means cataloguing where documents live, which identity groups map to which documents, which documents are draft or legal-hold, and which systems hold the authoritative ACL. The team should expect to spend one to two weeks on this phase, and the output is a schema decision, not a document: every retrieval unit needs tenant_id, a document id, an ACL representation such as group IDs or a sensitivity label, a version, and a status field. Without those columns, no downstream filter can be correct, and retrofitting them into a live index is more expensive than rebuilding the metadata early.

Then build the policy layer and prove it fails closed. Create a dedicated application role with no BYPASSRLS, no superuser status, and no ownership of the protected tables, and enable FORCE ROW LEVEL SECURITY so the table owner is also subject to policy. Wire the gateway to set the tenant and group session variables from the verified token inside a transaction, using SET LOCAL so pooled connections cannot leak state between requests. Write integration tests that attempt to read as a member of tenant A, a member of tenant B, a contractor, a service account, and an auditor, and assert that the row counts match the expected entitlements; a single unexpected row should fail the build.

Pilot with real permission shapes, not toy data. Over four to six weeks, connect one source system with two tenants and at least 12 distinct access personas, including a user in 50 groups, which is where filter selectivity starts to hurt. Measure the recall cost of filtering by comparing answers with and without RLS on the same questions, and if recall drops by more than about 10 percent, over-fetch a larger candidate set, for example 5 times the final k, before reranking inside the authorized set. Only after the pilot is clean should the team roll out to additional sources, and each source should get its own connector entitlement mapping rather than a shared permissive default.

## Common Mistakes and Their Replacements

The first common mistake is trusting a client-supplied tenant. If a request can set tenant_id, then the security model is an authentication problem wearing a database costume, and RLS will faithfully return whatever the caller asked for. The replacement is to derive the tenant from a signed token and to set the session variable server-side, rejecting requests whose claims do not match the route. The second mistake is using a bypass role in user-facing code; Supabase's service_role key and PostgreSQL's BYPASSRLS are for administration, so the application should connect as a restricted role and administration should go through audited break-glass accounts.

The third mistake is filtering after retrieval rather than before it, which is the same as trusting a doorman who checks tickets after the guests are inside. Filtering must happen inside the query, and the candidate set the reranker sees must already be authorized. The fourth is caching across principals; a cache keyed only on the question or the tenant will happily serve a restricted answer to a user who lacked the groups that produced it, so cache keys must include a hash of the user's entitlements and the policy version, and the safe default is to disable result caching for users with more than about 20 effective groups.

The fifth mistake is believing RLS covers everything. It does not apply to foreign tables, materialized views built outside the policy, data-warehouse copies, log exports, or embeddings that have already been exported to an engine with weaker controls, and it does not stop a document the user is authorized to read from containing a prompt-injection payload aimed at the model. The replacement is to treat every projection, cache, and export as a new security boundary with its own reconciliation job, to run a data-loss-prevention pass over ingested text, and to keep embeddings themselves classified as personal data, because a vector derived from a person's email address is still personal data under GDPR in most enterprise policies.

## When to Act and What It Costs

The timing question is easier than teams expect. If an assistant serves more than one customer, carries per-customer contractual promises, or touches data covered by SOC 2, ISO 27001, GDPR, or HIPAA, then RLS belongs in the first production release rather than a later hardening sprint. The thresholds that usually force action are concrete: more than 50 tenants makes manual filter review impractical, more than 1,000 users means group-based ACLs are in play, and a single document-per-user or project-scoped corpus makes per-principal authorization unavoidable. Teams that ship a shared index with no tenant key and plan to 'add security after the pilot' are the ones that end up rewriting the retrieval layer, and that rewrite typically costs more than building it correctly the first time.

On cost, the licensing picture in 2026 is favorable. PostgreSQL RLS is a built-in feature with no license fee, and a small managed Postgres instance for embeddings and metadata usually runs from about $25 to $400 per month depending on size and availability commitments. Managed vector services start near $70 per month for a serverless tier and rise with storage and query volume, while per-tenant isolation multiplies that figure by the tenant count, so a 200-tenant dedicated deployment can run into five figures per year before engineering time. The hidden cost is people: expect roughly 3 to 6 engineer-weeks to reach production for a mid-sized corpus, then ongoing 0.1 to 0.25 FTE for permission syncs, policy tests, and incident response. Compare that with the downside: a single cross-tenant disclosure in an enterprise deployment has been associated with contract terminations and six-figure remediation bills, so the engineering spend is cheap insurance rather than overhead.

The practical timeline is 4 to 8 weeks for a Postgres-native pipeline with one or two source systems, and closer to 3 months when permissions are federated across several identity providers or when regulatory review is required. Budget one week for the entitlement inventory, one to two weeks for schema and policy work, one week for the security test matrix, and one to two weeks for the pilot with real users. If the schedule cannot absorb those four pieces, the honest response is to limit the first release to a small number of tenants with dedicated storage, rather than to launch a shared index and promise filters later.

## Verifying, Monitoring, and Governing

Verification is what separates a policy that exists from a policy that works. The team should maintain a row-level test matrix that runs on every schema change and every permission change, covering at least 12 personas and asserting both allow and deny cases, and it should treat any cross-tenant row in a deny test as a build-breaking failure with a zero-tolerance threshold. A canary tenant with a unique marker document gives operations a live check that the filter is active in the running system, not just in the test database. Red-team exercises should attempt the obvious attacks, including swapping the tenant header, replaying another user's token, and using a contractor account, and the results should be logged with dates so reviewers can see when the last drill happened.

Monitoring should track the security-relevant metrics directly: the number of queries returned with an empty entitlement mapping, which should be 0 rather than merely small; the lag between an ACL change in the source system and its appearance in the vector metadata, which should stay under a few minutes for chat and can be minutes to hours for batch sources; and the recall delta introduced by filtering, which teams often find is 5 to 15 percent for heavy ACLs and can offset by over-fetching and reranking. Adding RLS typically adds single-digit milliseconds to query planning, so the p95 retrieval latency target of under 300 milliseconds for the database step is usually achievable, but it should be measured rather than assumed.

Governance closes the loop. Policies should be versioned, reviewed on a quarterly cadence and after every identity-provider change, and tied to a named owner in each source system. Break-glass access for support and legal should be separate, time-bound, fully audited roles rather than a standing bypass, and any request to disable RLS in production should require the same change process as a schema migration. Over time, the most durable posture is a layered one: coarse tenant isolation enforced in the database, fine group and label rules enforced in the query, encryption and DLP around ingestion, and output controls around generation. None of those layers is sufficient alone, and together they give an enterprise a defensible answer to the question auditors ask first, which is not whether the model is smart but whether it can be trusted to read only what the person in front of it is allowed to read.

## Quick answers

### Is PostgreSQL row-level security enough for a RAG system using pgvector?

For Postgres-native corpora, yes, because pgvector stores embeddings as a column on the protected rows, so ordinary RLS policies apply to them along with the metadata. The policy must be written on the chunks table and enforced with a non-privileged role, ideally with FORCE ROW LEVEL SECURITY enabled. RLS does not cover data once it has been exported to a separate vector engine, so any projection needs its own controls.

### What is the difference between RLS and filtering results after retrieval?

Filtering after retrieval happens once the unauthorized chunks are already in the candidate set and often in the model prompt, so it prevents some output leakage but leaves exposure in logs and intermediate context. It also reduces recall, because the top-k slots have already been filled with rows that are then discarded. RLS filters inside the query, so the model only ever sees authorized rows and recall is computed within the caller's entitlements.

### How do you propagate tenant identity from SSO to the database securely?

The identity provider issues a signed token with tenant and group claims, the API gateway verifies it, and the server sets a session variable such as SET LOCAL app.tenant_id inside a transaction using only verified claims. The tenant must never be read from the request body, a query string, or a client header, because those are attacker-controlled. A role with BYPASSRLS should never be used in user-facing code paths.

### How much does row-level security add to RAG latency and cost?

In PostgreSQL, policy evaluation typically adds single-digit milliseconds to query planning, and managed Postgres hosting for a mid-sized corpus generally ranges from about $25 to $400 per month. The larger cost is engineering: roughly 3 to 6 engineer-weeks to reach production, plus ongoing work to keep entitlements in sync. Per-tenant isolation is the expensive option, since managed vector services often start near $70 per month and multiply by tenant count.

### Can RLS handle documents shared across tenants or public data?

Yes, by combining row-level tenant policies with a second policy branch for explicitly public or shared content, or by placing shared documents in a separate table with its own permissive policy. The important step is making shared status an explicit, auditable field rather than a fallback that returns everything when the tenant check fails. Deny-by-default behavior should be tested, because a missing mapping should return zero rows rather than fall through to shared content.

Canonical: https://opensilo.co/knowledge/how_should_enterprises_enforce_row-level_security_in_rag_pipelines_in_2026.php
Markdown: https://opensilo.co/knowledge/how_should_enterprises_enforce_row-level_security_in_rag_pipelines_in_2026.php/index.md
