Multi-tenant AI agent: isolating each customer's data
Why is isolation harder with an AI agent?
Because an agent adds to ordinary software a component that decides for itself what to call, with which arguments, based on untrusted text. In a regular SaaS application, code applies a tenant_id filter to every query. In an agent, the model builds the query, and it can be influenced by a message, a document or a tool result.
OWASP documents the risk. LLM02:2025 (Sensitive Information Disclosure) recommends least privilege and limiting the model's access to external sources. LLM08:2025 (Vector and Embedding Weaknesses) describes cross-context leaks in databases shared across customers. LLM06:2025 (Excessive Agency) asks to run extensions in the user's context with the narrowest possible rights.
For the classic application side, our article on multi-tenant isolation in an HRIS covers the historical flaws. Here we focus on what the agent changes.
Where can one customer's data leak to another?
It leaks through four surfaces: tools, memory, RAG and shared context. Each has its own failure mode.
| Surface | Typical failure | Quick test |
|---|---|---|
| Tool calls | The customer ID is an argument the model fills in | Ask the agent to use another customer's ID |
| Long-term memory | Memory keyed by session or user only, without customer | Log in with two accounts from different customers and compare |
| RAG | A single index, filter applied after search or missing | Ask a question whose answer sits in another customer's corpus |
| Context and cache | Shared response or prompt cache, mixed logs | Replay the same request from two customers |
How do you isolate tool calls?
The customer must never be a tool parameter the model can choose. This is the most important rule, and the one most often broken.
A tool get_invoice(customer_id, invoice_id) exposes a direct object reference: if the model supplies customer_id, a prompt injection is enough to read another customer's invoices. The safe version is get_invoice(invoice_id). The server layer adds the customer identity from the authenticated session, passes it to the downstream service, and that service rechecks ownership.
In practice:
- Inject identity server-side. The token or request context carries the customer. The tool schema shown to the model has no such field.
- Apply complete mediation. OWASP asks to implement authorization in downstream systems rather than letting the LLM decide whether an action is allowed. Each service rechecks the customer on every call.
- Use credentials in the user's context. A narrowly scoped OAuth token per customer beats a single service account that sees everything.
- Prefer narrow tools. An "execute SQL" or "call any URL" tool bypasses every filter. Replace it with precise functions.
- Validate returned identifiers. If a tool returns an object whose owner does not match the session's customer, reject it server-side.
Our article on function calling explains why arguments are the main attack surface.
How do you isolate the agent's memory?
With a partition key that starts with the customer, then the user, then the session. Long-term memory is a database written by the model and read back later: treat it as such.
The flaws we meet:
- Global memory. A fact stored for one customer ("the accounting contact is X") surfaces for another.
- Untrusted writes. The agent memorizes text from a third-party document. That is a persistent injection, affecting every later conversation of that customer.
- Shared summaries. A conversation summary generated for one customer is reused as context for another.
The measures: partition physically or logically per customer, prefix every key, stop the model from choosing the partition, tag the origin of each memory (user, tool, document), and plan complete erasure at contract end.
How do you isolate RAG between customers?
With a mandatory metadata filter applied inside the vector query itself, or with one index per customer when the risk level justifies it. Filtering results after the search is fragile: the forbidden passages have already been ranked, counted and sometimes cached.
Three architectures, from simplest to most watertight:
| Architecture | Advantage | Limit |
|---|---|---|
| Shared index with metadata filter | Cheap, simple to run | One forgotten filter exposes everyone |
| Namespace or collection per customer | Clear separation, easy deletion | More objects to manage |
| Dedicated database or instance per customer | Maximum isolation | Cost and operational complexity |
Whichever you choose:
- Compute the filter from the server session, never from a field filled in by the model or the front end.
- Keep owner metadata on every passage from ingestion on.
- Mirror the sources' rights in the index: a document restricted to a group in the drive must be restricted in the database too.
- Check that deleting a document removes its vectors, its text copies and its cache entries.
Write-side risks, especially poisoning, are detailed in poisoned RAG: how to test it.
How do you test the isolation of a multi-tenant agent?
With at least two test accounts belonging to two different customers and a series of boundary-crossing attacks. Create marked data in each account (a unique string per customer) so any leak is unambiguous.
The tests, in order:
- Direct request. From customer A, ask for customer B's data by name.
- Identifier substitution. Ask the agent to call a tool with customer B's ID.
- Document injection. Plant in A a document that orders the agent to read and return another customer's data.
- Persistence. Make A store an instruction, then verify it appears nowhere in B.
- Cache and logs. Replay identical requests and verify no response or trace crosses customers.
- Enumeration. Try sequential or predictable identifiers in tools and source URLs.
Count the leaks, but above all record the path: a leak through the tool, through memory and through RAG call for three different fixes. As a rule, we read only what is needed to prove access: a canary string is enough, and we download no real personal data.
What should you keep in mind when designing the architecture?
That the model is never the security boundary. It can help phrase queries, but ownership checks happen elsewhere, using the authenticated identity.
A short checklist:
- No
tenant,customeroraccountparameter in the tool schemas shown to the model. - An identity context passed to downstream services, which recheck it.
- Memory partitioned per customer, with provenance.
- A RAG filter applied in the query, computed server-side.
- Logs that let you find which customer saw which passage. See logging an agent's actions.
- Automated regression tests between two test customers.
For the overall framework, see our AI agent security page.
How does CleanIssue audit multi-tenant isolation?
We replay the six tests above on your real agent, with two test tenants, and hand back reproducible evidence. The Agent Red Team Express at €1,900 covers one agent in 5 days. If you run several agents for several customers, the Custom Red Team is quoted based on scope. The first conversation is a free 30-minute discovery call.
Does your product ship an AI agent? CleanIssue tests what a stranger can make it do with the Agent Red Team Express (5 days, €1,900), or a custom red team for several agents and MCP servers.
Related articles
Three adjacent analyses to keep exploring the same attack surface.
Multi-tenant HRIS isolation: 7 flaws and how to test them
Editable organization_id, forgotten filters, shared caches, exports, the AI agent's vector index: multi-tenant HRIS isolation flaws and their tests.
Supabase multi-tenant: data separation checklist and tests
Editable org_id, views that bypass RLS, reusable invites, Edge Functions on service_role: the checklist to verify tenant isolation in a Supabase product.
Indirect Prompt Injection: When Your RAG Attacks You
Indirect injection hides orders in documents, pages or emails your RAG reads. Attack vectors, damage, defenses and a step-by-step testing protocol.
Sources
Related services
If this topic maps to a real risk in your stack, these are the most relevant CleanIssue audits.