Back to insights
Field Notes·9 min read

Your RAG Leaks: Permissions Are the Feature You Skipped

Your RAG Leaks: Permissions Are the Feature You Skipped

A vector search does one thing well: it finds the chunks most similar to a query. It has no idea that the person asking is a contractor, that the top result is an unreleased earnings figure, or that surfacing it is a reportable breach. Every RAG system we've audited that skipped authorisation worked beautifully in the demo, then shipped a permission leak into production. That's the gap between a clever retrieval trick and something you can actually put in front of HR or a multi-tenant customer base.

By Daniel Usvyat · Principal, USQRD

Share

The Index Doesn't Know Who's Asking

Embed a corpus, run cosine similarity, return the top-k. Nothing in that pipeline carries the concept of a user, a role, or a grant. The relevance score for "what's the exec team's bonus structure" points at the exact document you most need to withhold, high similarity and zero authorization are perfectly compatible.

We see this most often in HR and finance corpora. An employee handbook, a comp policy, and individual salary letters all embed into the same semantic neighbourhood. A junior engineer asks a benign question about parental leave and the retriever pulls a director's offer letter into context because it's textually adjacent. The model, doing its job, summarises it.

The failure is silent. Your answer-quality evals pass — the response is fluent, grounded, even cites a real source. Nobody complains, because the person who saw the leaked data usually didn't realise it was leaked. You find out when Legal does, or when a tenant sees another tenant's data. Complaint rate is a terrible proxy here, which is a broader problem with estimating an agent's real failure rate.

High similarity and zero authorization are perfectly compatible.

Pattern 1: Pre-Filter by ACL Before You Retrieve

The default we reach for. Attach the authorization facts to each chunk as metadata at index time, owning group, sensitivity tier, tenant ID, allowed roles, then push a filter into the vector query so the ANN search only ever considers documents the caller is entitled to see. Pinecone, Weaviate, pgvector, and the rest all support metadata filtering; the question is whether your permission model is simple enough to express as one.

The guarantee is strong: forbidden content is never retrieved, so it never reaches the model or the logs. The cost is that your access model has to be denormalised into filterable metadata, and it has to stay in sync. When someone leaves a group or a document gets reclassified, the index has to reflect it. A stale ACL on a chunk is the same leak with extra steps.

Two things to watch. First, recall: aggressive pre-filtering shrinks the candidate pool, and if your metadata is coarse you'll drop legitimately visible documents. Second, filter latency, high-cardinality metadata filters can force the index into a slow path, which matters once you're budgeting latency across the whole agent.

  • →Denormalise the authorization decision into chunk metadata at ingest, don't compute it at query time from a join.
  • →Re-index or patch metadata on every membership or reclassification change; treat ACL drift as a P1.
  • →Test recall with the filter applied, not on the unfiltered index, the numbers diverge.

Pattern 2: Post-Retrieval Permission Checks

Retrieve the top-k without an ACL filter, then check each returned chunk against the caller's permissions and drop the ones they can't see. This is the easiest pattern to bolt onto a system that already works, and it's the one we're most nervous about.

The forbidden chunk was already read out of your data store. It may sit in a query log, an APM trace, an LLM provider's request payload, or a semantic cache keyed on the query. "We filtered it before the model saw it" is not the same as "it never left the boundary." If your compliance posture treats retrieval as access, and for GDPR or SOC 2 purposes it often should, a post-hoc drop doesn't save you.

There's also a quality tax. If four of your top-ten are filtered out, you've quietly halved effective recall and the model answers from a thinner context than it thinks it has. Post-checks are fine as a defence-in-depth second layer behind pre-filtering. They're a poor primary control. If you do lean on them, wire the drops into your span-level tracing so a filtered retrieval is visible, not invisible.

Pattern 3: Per-User or Per-Tenant Index Partitioning

Give each isolation boundary its own index or namespace. A query against tenant A's namespace physically cannot return tenant B's vectors. The isolation is built into the structure, so there's no filter you might forget to apply or get wrong.

This is the right call for hard multi-tenant boundaries, where cross-tenant leakage is existential and one buggy filter clause ends the company. Namespaces in Pinecone, separate collections in Weaviate, or row-level security on a partitioned pgvector table all get you there. The strongest version is a separate index per tenant; the pragmatic version is a shared index with a mandatory namespace.

The economics are the reason not to reach for this by default. Thousands of sparse per-user indexes waste memory and fragment your ANN graphs, hurting recall on small partitions. Partition per company or per security domain, not per individual — inside a company, roles are better handled by Pattern 1. The data access layer is usually where these projects stall anyway, and partition sprawl makes that worse.

  • →Partition on hard boundaries (tenant, legal entity, security domain), not soft ones (role, team).
  • →Make the namespace a required query parameter with no default, a missing namespace should error, not fall through to the whole index.
  • →Watch recall on small partitions; a 50-vector namespace behaves differently from a 5-million one.

Agents Widen the Blast Radius

Everything above assumes a single retrieval step. An agent that plans, calls tools, and retrieves in a loop can launder a permission leak through several hops. It retrieves a forbidden chunk, summarises it into scratch memory, then that summary flows into a later tool call or a different user's shared session. The ACL lived on the original document; the derived summary carries none.

Enforce authorization at every retrieval and every tool call, using the end user's identity, not a service account that can see everything. Impersonation-based access, where the agent acts strictly as the calling user, is the pattern that survives contact with an audit. This is part of a broader stance we hold: treat agents like untrusted insiders, not trusted infrastructure.

Test it adversarially before launch. Craft prompts that try to coax the agent into surfacing another user's data, run them as a low-privilege identity, and assert the forbidden content never appears, in the answer or the trace. That belongs in the same use you use to stress-test the agent before it ships.

What's Still Hard, and Where to Start

The unsolved edge is derived and aggregated data. Row-level filtering handles "can this user see this document." It doesn't handle inference — a user with access to five innocuous facts assembling a sixth they shouldn't have, or an aggregate that leaks an individual's data even when every underlying row is permitted. We don't have a clean, general answer to that, and anyone who claims one is selling something. The honest move is to keep aggregation and cross-user summarisation out of scope for sensitive corpora until you can reason about it.

Start with an authorization model you can actually express as index metadata, and pre-filter by default. Add post-retrieval checks as a second layer, never the only one. Partition on hard tenant boundaries. Enforce the calling user's identity through every hop, and write an adversarial permission test into your acceptance criteria, before the build, not after.

A RAG demo that answers questions is a week of work. A RAG system that answers only the questions each user is actually entitled to have answered, that's the product, and it takes far longer to build. The permission layer isn't a hardening pass you tack on at the end. It decides whether you can ship at all.

Frequently asked questions

How do I enforce row-level permissions in a RAG system?

Attach authorization metadata (owning group, tenant, allowed roles) to each chunk at index time and push a filter into the vector query so the search only considers documents the caller can see. Pre-filtering before retrieval is safer than filtering after, because forbidden content never reaches the model, logs, or cache.

Is it safe to just filter out unauthorized results after retrieval?

Only as a second layer. By the time you drop the chunk, it's already left your data store and may sit in a log, trace, cache, or the LLM provider's request payload — which for GDPR or SOC 2 purposes can count as access. Use post-retrieval checks for defence in depth, not as your primary control.

When should I use a separate vector index per tenant?

For hard multi-tenant boundaries where cross-tenant leakage is existential. Structural isolation via namespaces or separate indexes can't be bypassed by a buggy filter clause. Don't partition per individual user inside a company — that fragments your index, hurts recall on small partitions, and wastes memory; handle intra-company roles with metadata pre-filtering instead.

Why does my RAG agent leak data even though the demo worked?

Because a vector search returns the most similar chunks with no concept of who's asking, and answer-quality evals pass whether or not the content was authorized. The leak is silent — the response is fluent and cites a real source — so you find out in an audit, not from a complaint. You need an explicit authorization layer, tested adversarially, before launch.

Free resource

Take the Operational Bottleneck Audit

Our Bottleneck Audit maps where your RAG or agent stack leaks — permissions, retrieval, or the data access layer underneath.

Ready to stop experimenting?

Find the leak before your auditor does

We audit enterprise RAG and agent systems for the permission and retrieval failures that pass every quality eval. Book a Bottleneck Audit and get a concrete remediation plan.

Book a Discovery Call
More insights