Can a user retrieve information outside their permissions, including through the lexical search path, caches, or indirect references?
TL;DR
- Yes, through any read path the permission check does not cover. The question names three: the lexical branch of a hybrid search, filtered differently from the vector branch; a cache that answers before the check runs; and indirect references, such as a fetch by ID or exact name, a pointer to a hidden record, or a scope value supplied by the request or the model.
- One rule addresses all three: the server derives the read scope from the verified caller identity and applies one filter on every path that can return stored content. For the model's part, the Databricks agent memory documentation says of the actor_id that scopes every read: "Never let the model choose it."
- Three probes, run as a user without access, test whether a system holds: a rare word from a restricted document through every search branch; a cache warmed by a user with access; and a direct fetch, a followed pointer and a foreign scope. Each needs a positive control run as a user with access, or an empty result proves nothing.
Mnemoverse, which publishes this library, makes one of the memory APIs quoted below.
Jeevan Srivastava asked the question word for word in a Medium article of 18 October 2026, as one of three dimensions to assess before production, beside retrieval quality and operational performance. He labels his access-control design: "These are proposed production design decisions, not features I'm claiming to have implemented as part of my retrieval fix."
Can a user retrieve information outside their permissions?
Yes, whenever a path that returns stored content skips the check that search runs.
A read path is any function, endpoint or query type that can return stored content to a caller: a search, an exact get, a list, an export, an admin surface or a debug endpoint. Heinrich Neb, who works on a memory layer for coding agents, gave the audit rule in a dev.to comment of 27 August 2026: "find every function that can return stored content."
| Read path | How it returns what the user may not see | What closes it | Documented by |
|---|---|---|---|
| Lexical branch of a hybrid search | The full-text query lacks the vector query's filter | One filter below both branches, or passed to each | Srivastava; PostgreSQL; Elasticsearch |
| Cache in front of retrieval | A stored answer is served before the check | The caller's scope in the key, or the cache behind the check | RFC 9111, for shared HTTP caches |
| Fetch by ID or exact name | A lookup outside search returns the record | An object-level check on every route that takes an ID or a name | OWASP API1:2023; Neb |
| Pointer from a visible record | A visible record names a hidden one | The target's visibility checked when the pointer is read | Neb; PostgreSQL |
| Scope from the request or the model | A user ID or tag from the path, body or a tool argument is trusted | Scope from the verified identity; supplied values checked against the configured set | Databricks; Supermemory; Mem0; Anis Meziani |
| Query language as a search type | Raw queries reach the store | Off unless every user of the deployment is trusted | Cognee |
The last four rows are kinds of indirect reference. Srivastava separates the two decisions: "Relevance determines whether information is useful. Authorization determines whether information is allowed to be accessed."
How does the lexical search path leak in a hybrid search?
The lexical branch is the half of a hybrid search that matches the query's words against a full-text index, such as BM25 or PostgreSQL full-text search, beside the vector half that matches embeddings.
Srivastava's rule: "The key decision is that both vector search and lexical search must respect the same access restrictions." The failure: "If vector search filters by tenant or document permissions but PostgreSQL full-text search doesn't, the hybrid retrieval pipeline can introduce a data leak through the lexical branch." On merging the two lists: "Combining rankings must never override authorization boundaries." And at retrieval time: "Apply authorization and tenant filters to every retrieval path."
One filter can reach both branches from two places.
Below both branches, in the database. With row security enabled on a PostgreSQL table, "all normal access to the table for selecting rows or modifying rows must be allowed by a row security policy", and a table with no policy falls back to "a default-deny policy". The same page sets two limits: "the table's owner is typically not subject to row security policies" unless the table is altered with FORCE ROW LEVEL SECURITY, and "Superusers and roles with the BYPASSRLS attribute always bypass the row security system when accessing a table." An application that connects as the owner of an unforced table, as a superuser or as a BYPASSRLS role is not filtered at all.
One filter passed to every branch. The filter parameter of the Elasticsearch RRF retriever "Applies the specified boolean query filter to all of the specified sub-retrievers, according to each retriever's specifications." Written once, it cannot drift between two copies.
OWASP's LLM08:2025 opens its prevention list with: "Implement fine-grained access controls and permission-aware vector and embedding stores." The OWASP text speaks of vector and embedding stores; a hybrid pipeline has a second index, and the same rule has to reach it. Srivastava adds a condition that holds over time: "metadata can become stale if permissions change and the retrieval index isn't updated accordingly."
Does a permission filter keep a restricted user's results complete?
Nothing leaks here; the risk runs the other way. The pgvector README: "With approximate indexes, filtering is applied after the index is scanned. If a condition matches 10% of rows, with HNSW and the default hnsw.ef_search of 40, only 4 rows will match on average." Its first suggestion, an index on the filter column, can give exact search in many cases, so the shortfall applies whenever the planner takes the HNSW index instead.
What follows is this page's inference for a vector branch on pgvector with an HNSW index, not a measurement. A permission rule is a condition that keeps some share of the rows, so the narrower a user's rights, the fewer rows one scan of the vector index can return, while the full-text branch can still return every permitted match. For hybrid search the README offers fusion: "You can use Reciprocal Rank Fusion or a cross-encoder to combine results." Fusion ranks the lists it receives, so a restricted user's top results draw more on keyword matches than an administrator's do. An evaluation run as an administrator measures only the administrator's ranking: report retrieval quality per permission level.
The README's levers: "Starting with 0.8.0, you can enable iterative index scans, which will automatically scan more of the index until enough results are found". On tenants: "For applications with multiple tenants, sharing an approximate index between tenants means vectors from one tenant can affect recall (and speed) for other tenants." And: "For tenant isolation, use list partitioning or separate tables." How a hybrid search merges its two lists is covered in the library page on duplicate results in vector retrieval.
Can a cache answer with documents the user may not see?
Yes, when it answers before the check runs. For shared HTTP caches, RFC 9111, section 3.5, sets the rule: "A shared cache MUST NOT use a cached response to a request with an Authorization header field" to satisfy a later request, unless the response allows shared storage through a Cache-Control directive such as public, s-maxage or must-revalidate. A semantic or answer cache inside an application follows whatever rule the application gives it.
Naveen Metta, in an article of 12 October 2026 on RAG before production, states the requirement:
If a user cannot open the document, the model should not be able to quote it.
From his team's work: "We found one prompt that could surface HR documents to any employee." He also advises: "Then add a semantic cache in Redis for repeated questions." This page's inference about any cache in front of retrieval, not a description of his system: such a cache answers without running the filter, so the caller's scope has to be part of what it matches on, or it has to sit behind the check.
Srivastava would test "whether permissions and deletion requests propagate to the relevant indexed and cached data." The warm, revoke, replay test is covered in the library page on revoked access after an answer was shown, and caches after a delete in Agent Memory Deletion: What Survives a Delete.
What counts as an indirect reference, and how does it leak?
An indirect reference is any way to reach a record other than a search the filter covers: its ID, its exact name, a pointer from another record, a scope value or a query language.
An existence leak is an answer that reveals that a hidden record exists, or something about it, without returning its content.
The fetch that skips search. Neb, on his own system: "We had scoping enforced correctly on retrieval, tested, verified", and beside it "a second read path, lookup by exact name rather than similarity, that returned full content to anyone who asked." In short: "Same store, two doors, one guarded." The sign in code: "a read path that doesn't accept the caller identity cannot enforce anything". For APIs, OWASP API1:2023 Broken Object Level Authorization: "Every API endpoint that receives an ID of an object, and performs any action on the object, should implement object-level authorization checks."
The pointer. On 25 August 2026, Neb reported a retired record whose banner kept naming its successor after the successor was made private: "Existence leak through the supersession pointer." His rule: "a pointer's validity and a pointer's visibility are both read-time questions, never write-time facts." PostgreSQL warns of a related channel: its referential integrity checks always bypass row security, and "Care must be taken when developing schemas and row level policies to avoid “covert channel” leaks of information through such referential integrity checks."
The query language. Cognee pull request #5844, merged into its dev branch on 15 October 2026: "CYPHER and NATURAL_LANGUAGE search types run raw Cypher against the graph database." And: "Any user with read access to a dataset could execute arbitrary queries". The change: "ALLOW_CYPHER_QUERY now defaults to false." For turning it back on: "Only do that when every user of the deployment is trusted."
What closes the class: a check at every entry point, and a refusal that neither names nor describes the hidden record, so that a missing record and someone else's record get the same answer. An answer that tells the two apart reveals existence, which is acceptable only where existence is not a secret.
Who should set the scope of a memory read: the server, the request or the model?
A caller-supplied scope is a value that selects whose data a read covers, such as a user ID, a tenant tag or an actor ID, and that arrives with the request or from the model instead of from the verified identity.
The Databricks agent memory documentation (Beta, last updated 26 October 2026) states the rule: "Set actor_id in your application code from trusted caller context: the verified end-user identity for per-user memory, or a trusted team or project key for shared memory. Never let the model choose it." And: "If your strategy depends on an end-user identity, reject requests that don't carry one rather than falling back to a shared actor_id."
Three classes of the error, each with a dated fix:
A scope chosen by the model. Supermemory pull request #1824, merged on 16 October 2026 into the 2.x release branch of its tools package: "In 2.x, getProfile, documentList, memoryForget and documentDelete accept an optional containerTag from the model and use it unchecked as the request scope." The consequence: "So a model, or a prompt-injected input, can name another tenant's tag and read their profile, list their documents, forget their memories, or delete their documents." The fix: "A model-supplied tag must be one of the tags the developer configured", or the call fails "before any request is sent". The pull request also notes: "Tools 3.x (#1773) already removed model-selected scope."
An empty scope read as everything. Mem0 pull request #7581, merged on 12 October 2026, on its hosted clients: "The dangerous one is an empty entity ID. The API treats an empty ID as matching every memory in the project." The case there is the delete calls, delete_all and delete_users, fixed on the client: "This PR blocks that client-side. The server needs its own fix, which is out of scope here." This page did not check whether the server has changed since.
A scope taken from the path or the body. Anis Meziani, replying on 14 October 2026 to a comment under his own dev.to article: "Our ADK-native session routes originally checked that a token was valid, not which user it acted for, so the user_id in the path or body was trusted." His fix checks the token's subject against the user_id "on every route that reads sessions or memory, including the live WebSocket".
The rule: the scope comes from the verified identity; a request without one is refused, not given a shared scope; an empty value is an error, not everything; a value from the model is checked against the configured set before any request goes out.
What changes when several users share one memory store?
In a shared store the question moves from the answer to the context. A preprint of 19 October 2026 (arXiv 2610.25707, version 1, not peer-reviewed) opens: "Shared long-term memory lets an LLM agent serving one user inadvertently retrieve another user's credentials, but it is unclear when such exposure leads to actual harm." Its audit used the AppWorld environment and one store for all users' logins: "In AppWorld, a store that mixes 763 past logins from all users retrieves another user's credential for 84.7 percent of required apps, yet agents rarely act on it." Of the misuse its stress test provokes: "Naming the owner before or inside each credential line prevents most of this". Its conclusion: "Agent memory should consistently record whose credential it stores, and evaluations should measure behavior, not only exposure."
This page's reading: the boundary is crossed when another user's record reaches the model's context, whether or not the model uses it. Srivastava, from the design side: "The stronger approach is to prevent unauthorized information from entering the context in the first place."
Databricks states where the boundary sits: "actor_id separates memories, but it is not an access control. Memory stores are workspace-scoped, so any principal that can reach a store can read and write every entry across all actors." And: "The store, not the actor, is the security boundary. For strict isolation between tenants or users, create a separate memory store per boundary." OWASP's LLM08:2025, for vector databases: "In multi-tenant environments where multiple classes of users or applications share the same vector database, there's a risk of context leakage between users or queries." How agents share memory and keep its boundaries is covered in Shared Memory for Multi-Agent Systems.
How do you test that no read path leaks?
Each probe has a positive control, because a probe that finds nothing proves nothing until it finds something where it should. They are this page's proposals, not any team's suite.
- The lexical branch. As the test user, run vector-only, lexical-only and hybrid queries for a rare word, such as a code name, found only in a document the user may not open. Expected: no chunk of it. Control: a user with access gets it. Srivastava proposes this negative test: "Queries from unauthorized users should not expose protected documents, even when those documents are semantically similar to the query."
- The cache. User A, with access, asks a question whose answer needs the restricted document; user B, without access, asks it and a paraphrase. Expected: B's answer is not built from that document. Control: A's answer is built from that document, and B's answer with the cache off matches B's answer with the cache on.
- Indirect references. As a user without access, fetch the record by ID and by exact name, follow a pointer to it from a visible record, and send another user's scope and an empty scope. Expected: the same answer as for a record that does not exist; a different answer reveals existence, which passes only where existence is not a secret. Controls: as a user with access, the same fetch, pointer and scope return the record, so the path is live; a made-up ID on the same path gives the answer to compare against.
Three rules for the run:
- Take the list of read paths from the code or the tool registry, not from a filtered listing. sam ros, on 15 October 2026, about the CI test of the scope check in his MCP server: "with a list_tools override that filters by the token's scopes, the old test passed while a tickets:read token could still delete."
- Rerun the probes after a permission change and after a delete: permission data copied into an index or a cache can go stale.
- Inspect what reached the model's context, not only the final answer.
A passing first probe says nothing about the second and the third.
How do memory APIs document where the boundary is?
Two documented positions, in their own words, as read on 1 November 2026.
Databricks. The store is the boundary, and application code, not the model, sets the actor ID; both are quoted above.
Mnemoverse. The security page states the tenant boundary: "Every API key is bound to a unique organization_id. All database queries are scoped to your organization." Shared rooms (beta) stay apart: "room content never appears in your normal (non-room) searches". The rooms reference covers indirect references: "Knowing a room id grants no access to its contents: access is enforced by the API's membership check on every request, and non-members get 403." And: "Membership is checked before archive state, so a non-member of an archived room still gets the membership 403", so the answer does not reveal whether a room is archived. The same page notes what a bare id does reveal: "The one thing a bare id does reveal is existence", since 404 and 403 differ. This is a documented boundary, not a test result; this page tests no product.
What this page does not claim
No measurement of any product and no figure for how often any system leaks. The numbers on the page are the pgvector README's illustration and one preprint's audit, each with its conditions. Vendor fixes are quoted from their own pull requests with their merge dates; this page did not check whether a fix has reached a release. The inference about rank fusion is for pgvector with an HNSW index and was not measured. The probes are proposals, not a description of any team's suite. Product documentation, including Mnemoverse's, is quoted for what it states as read on 1 November 2026.
Common questions
Can a user retrieve information outside their permissions, including through the lexical search path, caches, or indirect references?
Yes, through any read path the permission check does not cover. The question names three: the lexical branch of a hybrid search filtered differently from the vector branch, a cache that answers before the check runs, and indirect references such as a fetch by ID or name, a pointer to a record the reader cannot see, or a scope value the request or the model supplies. The server should derive the scope from the verified caller identity and apply the same filter on every path that can return stored content.
Does the full-text branch of a hybrid search need the same permission filter as the vector branch?
Yes. Jeevan Srivastava's rule is that both branches must respect the same access restrictions; otherwise the lexical branch returns what the vector branch filtered out. Rank fusion cannot repair that afterwards. A filter applied once below both branches, such as a PostgreSQL row security policy that binds the role the application connects as, or one filter passed to every sub-retriever, removes the chance that the two drift apart.
Can a cache return an answer built from documents another user cannot open?
Yes, if the cache answers before the permission check runs and its key does not carry the caller's scope. HTTP defines a rule for shared caches and the Authorization header; an answer or semantic cache inside an application follows only the rule the application gives it. Key it on the scope as well as the question, or place it behind the check, and test it as two users.
Is hiding a record from search results enough to protect it?
No. A record can still be reached by its ID or exact name, through a pointer from a record the reader may see, or through a query type that runs past the search filter. OWASP's API Security Top 10 asks for an object-level authorization check on every endpoint that receives an object's ID. A refusal should also not name or describe the record it hides.
Should the model choose which user's or tenant's memory to read?
No. Set the scope in application code from the verified caller identity; the Databricks agent memory documentation says of actor_id: "Never let the model choose it." If a tool accepts a scope from the model, check it against the scopes configured for that caller before any request is sent, and reject an empty value instead of treating it as everything.
How do I test a RAG system or agent memory for permission leaks?
Run three probes as a user without access, each beside a positive control run as a user with access: a rare word from a restricted document through every search branch; the same question after a permitted user has warmed the cache; and a direct fetch, a followed pointer, a foreign scope and an empty scope. Repeat after a permission change, and check what reaches the model's context, not only the final answer.
Sources
Every page below was read on 1 November 2026. Each quotation on this page is contiguous text of the page it comes from.
The question and practitioners
- Jeevan Srivastava, Building Production RAG: Retrieval Quality, Privacy, and Engineering Trade-offs, Medium, 18 October 2026: the question in the title, the rule for both search branches and the tests he proposes.
- Naveen Metta, 10 RAG Concepts Every AI Engineer Should Learn (Before Shipping to Production), Medium, 12 October 2026: permission-aware retrieval and the semantic cache.
- Heinrich Neb, dev.to comments under articles by this page's author: 27 August 2026, the second read path; 25 August 2026, the supersession pointer.
- Anis Meziani, reply of 14 October 2026, dev.to, to a comment under his own article: the user_id from the path or body.
- sam ros, reply of 15 October 2026 to this page's author, dev.to, under his article Your MCP server probably accepts tokens that were never meant for it: the test that enumerated a filtered list.
- This page's author, response of 18 October 2026 to Jeevan Srivastava's article, Medium: the origin of the inference about rank fusion and narrow permissions.
Standards
- OWASP API Security Top 10 2023, API1:2023 Broken Object Level Authorization.
- OWASP Top 10 for LLM Applications 2025, LLM08:2025 Vector and Embedding Weaknesses.
- IETF, RFC 9111, HTTP Caching, section 3.5, Storing Responses to Authenticated Requests.
- PostgreSQL documentation (current, version 18 as read), section 5.9, Row Security Policies.
Search and vector databases
- pgvector, README, master branch, install instructions at v0.8.7: the sections Filtering, Multitenancy, Iterative Index Scans and Hybrid Search.
- Elasticsearch reference, RRF retriever: the
filterparameter.
Memory systems
- Databricks, Agent memory, Beta, shown as last updated on 26 October 2026.
- Supermemory, pull request #1824, merged on 16 October 2026 into release/tools-2.x.
- Mem0, pull request #7581, merged on 12 October 2026 into main.
- Cognee, pull request #5844, merged on 15 October 2026 into dev.
Research
- Hau-Sing So and co-authors, arXiv 2610.25707, version 1 of 19 October 2026, cs.AI, a preprint: the abstract.
Mnemoverse
Related
- If persistence is not permission, where would you have drawn the line: cards only, or the entire turn?: revoked access, history replay, and the warm, revoke, replay test for a cache.
- Agent Memory Deletion: What Survives a Delete: what a delete removes and what remains, including cached answers.
- Why does my vector DB retrieval keep returning duplicate noise for my chat agent, and how to fix it?: how the keyword and vector lists of a hybrid search are merged.
- Shared Memory for Multi-Agent Systems: private and shared memory, isolation and boundaries between agents.
- Rooms: the Mnemoverse shared-memory rooms reference (beta).
- Security: Mnemoverse tenant isolation and room membership.
Edward Izgorodin · Mnemoverse · 2026-11-02
