A shared search index can show a user code they have no right to read. The fix is to store an access label with every chunk and filter results by the asking user's permissions before anything reaches the model. Do not rely on the prompt to hide things.
The problem
You index ten repositories into one store. A developer who can read only three of them asks a question. The nearest chunks, by meaning, come from a restricted repository. The model summarises it helpfully. You have just bypassed your own access controls with a search box.
OWASP's list of vector and embedding weaknesses names this directly. It describes weak access controls exposing sensitive embeddings, and cross context leaks in multi tenant vector stores, where one group's data can surface in another's queries.
Why prompts do not fix it
Telling the model "do not reveal restricted content" is not an access control. OWASP's guidance on sensitive information disclosure warns that restrictions placed in a system prompt may not always be honoured and could be bypassed via prompt injection. If the text was retrieved and sent, it has already left the boundary.
The check must happen before generation.
Design: label, filter, verify
- Label at index time. Store the source repository, and a group or access list, on each chunk.
- Resolve the user. At query time, find which repositories this user may read, from your source control system or identity provider.
- Filter in the search. Apply the filter inside the query, not after, so top results are drawn only from allowed chunks. With a relational vector store this is an ordinary WHERE clause. See Postgres with pgvector or a dedicated store.
- Re-check at read. Optionally verify against the live permission source before showing a snippet, since access can change after indexing.
- Log retrieval. OWASP recommends detailed, immutable logs of retrieval activity to detect suspicious behaviour.
Pitfalls
- Filtering after search. If you take the top 20 and then drop the disallowed ones, you may be left with two results. Filter first, or over fetch.
- Stale permissions. A person leaves a team, and chunks keep their old label. Sync labels on a schedule or resolve groups live.
- Derived data. Summaries, caches and saved answers can contain restricted text. Give them the same labels.
- Shared caches. A cached answer to one person's question must not be served to someone without access.
- Secrets in code. Credentials committed by mistake get indexed too. Scan before you embed, see what to scan before you index.
Separate indexes as a blunt option
For very sensitive material, use a separate index per sensitivity tier and give users access to whole indexes. OWASP suggests partitioning data logically and by access level. It costs more to run but is easy to reason about.
Test it like an attacker
Make a test user with access to one repository. Ask questions designed to pull from another, using distinctive phrases that appear only there. If anything comes back, the filter is broken. Repeat after every change to the pipeline. Also see securing vector databases.
Quick check
Is a per team index enough?
It is a good start for coarse boundaries. Add chunk level labels if teams share repositories or if access is finer than team.
Do local models change this?
Running locally keeps data on your machine but does not decide who may see what. Access rules still belong at retrieval.
Sources
Last reviewed: 19 October 2026.