A Filter Is Not an Authorization System
Most teams building retrieval over private data arrive at the same design, usually within an afternoon of realising they have a problem. Put the permissions on the chunk. Store the groups that may see it in the vector payload. Compile the caller's groups into a `where` clause. Push the filter into the search itself so the database never returns a row the user should not see.
This is a good design. It is the right shape, it is cheap to build, and it solves the most visible version of the problem, which is that embeddings strip the access controls your source systems spent years enforcing. If you have built this, you have built something real. Filtering during the search rather than after it is meaningfully better than the alternative, and it is worth saying that plainly before saying anything else.
It is also the easy half.
The four assumptions inside a filter
A compiled filter is a function of three inputs: who is asking, what they are entitled to, and what the data is tagged with. The filter itself is usually correct. What decays is everything it depends on.
**It assumes you know who is asking.** Most filter layers take a user object from the calling application: an id, a list of groups, maybe a department. That is an assertion, not a verification. The application passing it may be reading a session that was minted an hour ago, or a token cached at process start, or a config file written last quarter.
**It assumes entitlements are current.** Group membership changes. People move teams, contractors roll off, someone is offboarded on a Friday. A filter compiled from a list that was accurate when the request began will happily produce a confident, wrong answer.
**It assumes the tags are right.** This is the load-bearing one and we will come back to it, because it is where the honest version of this post stops being comfortable for us as well.
**It assumes nobody will ask afterwards.** Six months later, someone asks what a particular person could have retrieved on a particular day. A filter is a mechanism, not a record. If the only evidence is that the query returned four rows, that question has no answer.
None of these are bugs in the filter. They are the parts of an authorization system that a filter does not attempt, which is fine right up until the moment you need them.
Who tags the data
Here is the question we could not write around, so we went and checked our own code before writing anything else.
In Gateco, classification, sensitivity, domain and labels are supplied by the caller at ingestion time. Every one of them is optional. If you register existing vectors in bulk, the defaults come from your request. Nothing inspects a document and decides it is confidential. We ship a rule-based tool that suggests classifications by pattern matching, and it is deliberately a suggestion: an administrator reviews and accepts them, and it never applies anything on its own.
So on the specific question of tag correctness, we are in the same position as the filter you would write yourself. The tags are asserted by whoever put the data in. If a document is mislabelled, we will enforce the wrong thing precisely and quickly.
We would rather write that down than let a reader discover it. A vendor telling you that tagging is solved is telling you something about the vendor.
What changes is where the other three assumptions live.
Identity, evaluated rather than accepted
Principals in Gateco come from your identity provider and are keyed on the provider's own stable identifier, not on an email address. Policy conditions read group membership, roles and attributes at the moment the query is built.
The practical difference shows up in the boring cases. When someone's email changes, their access does not, because nothing in the enforcement path keys on email. When a principal is deactivated, the next retrieval is refused even if the calling application passes a principal id it cached earlier, because the check happens on our side of the boundary rather than in the caller's assumption.
Two things we should state without being asked. The calling application still asserts which principal a request is for; we verify that principal's current state, we do not authenticate the end user for you. And an identity provider that we poll on an interval is only as current as its last sync.
Change, and what "immediately" actually means
Revocation takes effect on the next retrieval. That is the honest phrasing and it carries two carve-outs that belong in the same breath rather than in a footnote.
The first is the sync interval above: if the provider is polled, a membership change is visible to us when we next poll, not when it happens. The second is that any permissions imported from an external system propagate when that import next runs, the same as they would anywhere else. We are not immune to the general problem that a copy of a permission is stale the moment the original changes.
What we do not do is bake entitlements into the index at write time, which is the version of this problem that requires a reindex to fix.
Provability
Every retrieval decision is recorded with the inputs that produced it: the principal, the policy that matched, what was allowed and what was withheld. The audit surface is not only retrieval, it covers the control plane too, so policy changes and provisioning are recorded alongside the decisions they explain.
This is the assumption a filter is least able to cover, and it is usually the one that turns out to matter, because it is what lets you answer a question after the fact rather than reasoning about what the code would have done.
What this costs
Doing the check in a layer rather than in the query costs something, and the number is worth stating rather than hand waving. In our published benchmark the policy layer adds 16ms at p50 and 21ms at p95 on top of the vector query, measured on a development machine. Across five recorded runs the p95 ranges from 13.55ms to 26.2ms. The runs at the high end carry stall outliers many times their own p99, which is host contention rather than the software, and we publish those runs alongside the good ones. The figure is the service-side policy layer, not end-to-end retrieval, and it excludes query embedding.
You do not have to take our number on trust. Every Gateco account, including the free one, has a Performance Self-Test page that measures the same quantity against the running service and reports it back to you, using your own policies and your own recorded retrievals.
Where this leaves you
If you have built the filter, keep it. It is the right primitive and it is doing real work.
The question worth asking is not whether the filter is correct. It is what happens to the three things underneath it as your organisation changes: whether the identity in the request is verified or assumed, whether entitlements are read live or compiled from a snapshot, and whether anyone can reconstruct a decision after the fact.
Those are answerable without buying anything. They are also the parts that quietly stop being true months after the filter was written and tested, which is why they are worth answering now rather than during an incident.
Related reading
Ready to secure your AI retrieval?
Start with the free tier: 1,000 retrievals/month, no credit card required.