Context Security
Prompt injection is text arriving that should not steer the model; this is data sitting in the window that should not be there at all. The tenant filter belongs inside the retrieval query, and the payload you assembled now exists in four other systems.
The other direction
Prompt injection covers text arriving in your window that should not steer the model. This page is the reverse: data sitting in your window that should not be there at all, and the copies of it you did not mean to make.
Both are context problems. Only one of them has an attacker, which is why this one tends to go unexamined until an audit finds it.
Everything in the window is one blast radius
A payload is assembled from several sources and then concatenated. After that, the model treats it as one document, and so does anything downstream.
The consequence: a secret in your system prompt, a private record pulled in by retrieval, and the user's question are now equally reachable. A model asked to summarize its instructions will have a go. A model quoting a retrieved passage will quote whatever was in it.
So the question to ask of every block you assemble is not "is this useful" but "would I be comfortable with the user seeing this". If the answer is no, it does not belong in the window, however convenient it is there.
Secrets do not belong in a prompt
API keys, internal hostnames, connection strings and signing secrets go into system prompts routinely, usually so a tool description can reference them.
They do not need to be there. The model asks for a tool call; your code holds the credential and makes the request. The model never needs the key to use the capability, and a key it never sees is one it cannot leak, log, or be talked out of.
The same logic applies to internal detail that is embarrassing rather than dangerous. A prompt naming your fraud thresholds is a prompt that can describe them.
Retrieval is the leak nobody tests
The sharp edge here is multi-tenancy, and the bug is always the same shape: a filter applied in the application but not in the query.
A vector store holding documents for every customer will return the nearest neighbours regardless of who asked, unless the tenant is part of the search itself. Post-filtering results after retrieval looks identical in testing, where every result belongs to the asker anyway, and fails the first time a query's true nearest neighbour belongs to someone else.
Two things follow. Filter inside the query, not after it. And write the test that asserts a tenant cannot retrieve another tenant's document, because nothing about normal use will surface this.
Personal data is the same problem with a legal dimension. A document that was fine to index may carry personal information that is not fine to send to a third party, and indexing is where that decision gets made silently.
Every copy is a second system to protect
This surprises teams who treated the window itself as the whole problem.
The payload you assembled now exists in more places than the request:
- Your observability backend, if you record prompts and completions. LLM observability covers that tension directly, and the default in most libraries is to capture content.
- Your provider's logs, subject to whatever retention their terms specify. Zero-retention arrangements exist and are not the default.
- Prompt caches, which hold a prefix server-side to be matched against later requests.
- Memory stores, which the model writes and reads on later sessions. External memory notes that Claude usually refuses to write sensitive data, and usually is not a guarantee.
A regime that treats the request as the sensitive artefact and ignores the four copies is not a regime. Decide retention for each before you instrument, because retrofitting redaction across four systems is harder than not writing the data.
Questions worth answering before launch
Five, in the order they tend to bite:
- If a user asked the model to repeat its instructions verbatim, what would they learn?
- Is the tenant filter inside the retrieval query or applied to its results?
- What does the observability backend store, and for how long?
- What does the provider retain, and does the contract say so?
- Who can read the memory store, and what has the model written into it?
None of these needs an attacker to go wrong. That is what separates them from injection, and why they are worth a separate pass.
Further reading
- Prompt injection attacks: the adversarial half of context security.
- Context isolation: keeping what you wrote separable from what you fetched.
- LLM observability: why recording the useful thing creates a second store of customer data.
- External memory: the store the model writes to.
Knowledge check
Question 1 of 4
Sign in to save your progress and pick up where you left off.