---
title: "Can one user's chat leak into another's?"
description: "It can, but rarely through the model. Leaks come from a shared cache whose key omits who asked, from logs, or mixed-up requests. Put the user in the key."
topic: "AI systems"
chapter: "trust-the-answers"
order: 16
minutes: { plain: 6, deep: 18 }
prerequisites: ["Reading short Python functions", "What prompt caching is"]
seo_title: "Can an AI chatbot leak one user's chat to another?"
date: 2026-10-19
---

# Can one user's chat leak into another's?

Take a shop's help bot with a cache, a shelf of ready answers kept to save work. In a toy run of 14 requests from three made-up customers, the shelf answered 10 of them without asking the bot, and 6 of those answers went to the wrong customer: order codes and streets crossed between people. The bot's model mixed nothing up. The shelf did.

## In one minute

Yes, a user's chat can leak into another's, but usually not through the model: it leaks through the software around it, such as a cache that forgets who asked. Picture a pharmacy that keeps ready bags on a shelf labelled only with the medicine's name. The next person who asks for that medicine gets someone else's bag. A cache does the same when its key, the label an answer is filed under, leaves out the user. The plain fix is to put the user in every key and keep private text out of shared places.

## Explore the numbers

[Open the widget](https://spicyshark.co/learn/can-one-users-chat-leak-into-anothers/widget.html). It opens on the shared shelf, with every wrong answer marked LEAK. Tap a request to see who asked, the key looked up and whose answer came back. Tick Put the user in the key and watch the leaks go and the hit rate, the share of requests the shelf answers, fall. Under More settings, see how a short wait can give away what the server kept.

## How it works

### What does not leak

While a model writes an answer, its weights, the numbers it learned in training, do not change. It also keeps nothing between replies: the app sends the chat again each time, as [Why does a chatbot forget?](https://spicyshark.co/learn/why-chatbots-forget/#the-model-keeps-nothing-between-replies) shows. So the model does not carry one user's chat into another's. Whether a provider later trains on chats is a separate question of settings and policy, and this lesson leaves it aside.

### A shelf that forgets who asked

A response cache keeps whole answers, so a repeated question skips the bot. It finds an answer by its cache key. If the key is the question alone, the second person to ask "Where is my order?" gets the first person's answer. That is a hit, a request the shelf already holds, and here a leak. User A asks first, the shelf keeps Order A-1042, and User B is handed it.

![Shelf keyed by question alone: User A's answer goes to User B.](https://spicyshark.co/learn/can-one-users-chat-leak-into-anothers/figs/shared-shelf.svg)

*Figure 1. The key has no user in it, so User B's request finds User A's answer.*

Over the 14 requests, this key gave 10 hits, 6 of them leaks, a hit rate (the share of requests the shelf answered) of 71%. The answer to opening hours is the same for everyone, so that hit does no harm. The leaks are the answers that carry a private fact.

### Putting the user in the key

Now file each answer under the user plus the question. User B's key, "B | Where is my order?", differs from User A's, so B misses and the bot writes B's own answer. The same 14 requests give 5 hits, no leaks and a hit rate of 36%. The key gives up one harmless hit, opening hours, because the toy cannot tell a public answer from a private one.

![Shelf keyed by user plus question: two separate entries.](https://spicyshark.co/learn/can-one-users-chat-leak-into-anothers/figs/user-in-key.svg)

*Figure 2. Each user reads only their own entry.*

### A wait can give a cache away

A prompt cache is different: it returns the right answer to everyone. A hit means the server skips reading a stored start, the opening of an earlier prompt that it kept, so the first word comes sooner ([What is prompt caching?](https://spicyshark.co/learn/what-is-prompt-caching/#what-the-server-keeps), [time to first token](https://spicyshark.co/learn/why-ai-waits-before-the-first-word/#two-numbers-to-watch)). If users share the stored start, User B can learn from a shorter wait that someone recently sent it. In the toy a hit waits 7.9 milliseconds (ms) at best and a miss 52.5 ms. Random delay hides a single try: the quickest of three guesses is the real one 68% of the time, and 99% after averaging 25 waits each. Chance is 33%. Mix the user into the key, as a value called a cache salt, and every guess waits the same: 33% either way. Real timing is noisier, and this is a simulation.

### Two more routes

Logs, the records a system writes of what it did, can leak: if they keep every full prompt and answer, anyone who can read them reads every user's chat. A mix-up between requests can leak too. In March 2023 OpenAI reported that a bug in an open-source library, used with its cache of user information, could return data from a connection left out of step by a cancelled request, so that data meant for one user reached another. Some users saw titles from another active user's chat history, and payment details of 1.2% of ChatGPT Plus subscribers active in a nine-hour window may have been exposed. OpenAI added checks that cached data matches the requesting user.

![Three leak routes with fixes, and the model, which does not carry a chat.](https://spicyshark.co/learn/can-one-users-chat-leak-into-anothers/figs/three-routes.svg)

*Figure 3. Dashed orange: routes that leak. Solid grey: the model.*

### What this toy simplifies

Real caches store pieces of prompts, expire entries and are separated by the provider in ways a customer cannot see. The shop, the users and every number here are made up.

## What this means when you build

- **Put the user in every key.** Use the account or user in each response cache key, and in the prompt cache's key or salt where your provider or server lets you set one.
- **Never cache personal answers across users.** Share a key only for answers that are the same for everyone, such as opening hours.
- **Log less and keep logs private.** Hide private details, limit who reads the logs and delete them soon.
- **Test with two accounts.** Have both ask the same question, and check that neither gets the other's answer.
- **Ask your provider how its caches are separated.** By organization, by workspace, or by user?

## Go deeper

*Optional. This part is for readers who write Python. The code is in the `code/` folder next to this lesson. From inside that folder, `python3 show_numbers.py` prints every number below, and `python3 -B -m pytest -q -p no:cacheprovider` runs the tests.*

### The toy shop

Three made-up users each have an order code and a street: A (A-1042, Mill Lane), B (B-2210, Quarry Road) and C (C-3377, Orchard Close). The bot answers four questions: where is my order, what is my delivery address, what are your opening hours, and can you gift-wrap order N. Three answers carry a private fact. Opening hours are the same for everyone. The fourth question holds the asker's own order code, so two users never type the same words, and a repeat of it is a safe hit in both modes.

The 14 requests come in a fixed order. In the tables, Shared is the key of the question alone and Keyed is the user key, the user plus the question. In the first table, "order", "street" (the delivery address), "hours" and "gift" (gift-wrap) name the four questions. "hit 3" means the cache returned the answer written at request 3, and "LEAK 1" means it returned the answer written at request 1 for a different user.

| Request | Shared | Keyed |
|---|---|---|
| 1. A: order | miss | miss |
| 2. B: order | LEAK 1 | miss |
| 3. A: hours | miss | miss |
| 4. C: hours | hit 3 | miss |
| 5. B: street | miss | miss |
| 6. A: street | LEAK 5 | miss |
| 7. C: order | LEAK 1 | miss |
| 8. A: order | hit 1 | hit 1 |
| 9. B: gift | miss | miss |
| 10. B: gift | hit 9 | hit 9 |
| 11. C: street | LEAK 5 | miss |
| 12. B: order | LEAK 1 | hit 2 |
| 13. A: hours | hit 3 | hit 3 |
| 14. C: order | LEAK 1 | hit 7 |

### The two keys in code

The two modes differ in one line. A miss stores the right answer for the asker; a hit returns whatever is stored, and a leak is a hit whose answer is not the right one for this user. The cache never expires.

```python
def cache_key(user: User, text: str, per_user: bool) -> str:
    return f"{user.name} | {text}" if per_user else text


def simulate(per_user: bool, sequence=SEQUENCE) -> list[Step]:
    store: dict[str, tuple[str, int]] = {}
    steps = []
    for n, (name, q) in enumerate(sequence, 1):
        user, text, right = USER_BY_NAME[name], question(USER_BY_NAME[name], q), answer(USER_BY_NAME[name], q)
        key = cache_key(user, text, per_user)
        if key in store:
            served, by = store[key]
            steps.append(Step(n, name, text, key, True, by, served, right, served != right))
        else:
            store[key] = (right, n)
            steps.append(Step(n, name, text, key, False, None, right, right, False))
    return steps
```

These are the real functions, with their comments left out. `leak_sim.py` has the full file.

### The counts

With the shared key the 14 requests give 10 hits, 6 leaks and a hit rate of 71%. With the user in the key they give 5 hits, 0 leaks and 36%. The first rate is flattering, because 6 of its 10 hits were wrong. Count only hits that returned the right answer, and in this sequence the user key does not lose: 5 against 4. It gives up request 4, the harmless hours hit, and wins requests 12 and 14, which were leaks under the shared key. A shared key is fine for an answer that is the same for everyone, but the toy does not separate those, so its user mode gives that hit up.

### The timing toy

Each prompt has 900 tokens that start every request of the shop's bot, 100 tokens of the user's note and 20 tokens of the new question: 1,020 in all. A token is a small piece of text. Suppose User A's note names Mill Lane and the server stored the first 1,000 tokens. User B sends the same instruction with a note naming a guess: Elm Road, Mill Lane or Fern Close.

Without the user in the key, B's right guess begins with the whole stored start, so only its last 20 tokens are read. A wrong guess matches only the 900-token instruction, which is less than the whole stored start, and a stored start is reused [whole or not at all](https://spicyshark.co/learn/what-is-prompt-caching/#a-stored-start-is-reused-whole-or-not-at-all), so all 1,020 tokens are read. Best-case reading times come from the `prefill_sim` code of [lesson 23](https://spicyshark.co/learn/why-ai-waits-before-the-first-word/): 7.9 ms for 20 tokens and 52.5 ms for 1,020. With the user in the key, a start is stored and found only under the same user, so B's guesses all read 1,020.

Every wait also gets a random queueing delay, drawn from an exponential spread with a mean of 60 ms, a toy figure. One experiment draws 25 waits for each guess. The first table gives the average wait in ms over 2,000 experiments, with the fixed seed 2026 and the same draws in both modes, so only the key differs (Shared: the question alone; Keyed: the user plus the question):

| Guess | Shared | Keyed |
|---|---|---|
| Elm Road | 112 | 112 |
| Mill Lane | 68 | 112 |
| Fern Close | 112 | 112 |

Then User B picks the guess with the shortest wait, from one try each or from the average of 25. In how many experiments is that the real street?

| Waits used | Shared | Keyed |
|---|---|---|
| One try | 68% | 33% |
| 25 tries | 99% | 33% |

![Average waits for three guesses with a shared key and with the user in the key.](https://spicyshark.co/learn/can-one-users-chat-leak-into-anothers/figs/wait-by-guess.svg)

*Figure 4. Only the real street waits less, and only with a shared key. Green is a stored start reused; dashed orange is a start read again.*

The gap between a hit and a miss is 44.6 ms, and the delay has a mean and a spread of about 60 ms, so one try often hides it. An average of n draws varies about 1/sqrt(n) as much, so 25 tries cut the spread to about 12 ms, well under the gap. With the user in the key there is no gap, and no number of tries finds one: 33% is a blind guess. The toy resets the cache before every try, so that each try sees the same start. A real cache changes with every request. It would store each try too, which changes what the next try sees.

### What the toy leaves out

Real prompt caches store a prompt in blocks, expire entries after minutes and are kept apart by the provider, by organization and sometimes by workspace, in ways a customer cannot see. With blocks, a wrong guess would still reuse the shared 900-token instruction and read only 120 tokens. The toy's model puts those 120 tokens at 7.9 ms, like the right guess's 20, because a short pass waits on memory, not on arithmetic. A toy with blocks would then show a gap of 0.0 ms, so the toy's 44.6 ms gap comes from its whole-or-nothing rule. That does not make a shared cache safe: a real gap grows with the length of the part that differs, so a small one takes many samples to see. Real timing includes the network and the queue, which is far noisier than this delay. The toy only shows the mechanism, a timing side channel, which means learning a hidden fact from how long a system takes: a shared start can be seen from the wait, and the user in the key removes it. Gu and colleagues audited real APIs with statistical tests over many time-to-first-token samples, and reported finding cache sharing across users in some. They argue that to completely prevent privacy leakage from prompt caching, only per-user caching should be allowed, and that providers should say what their policy is. The authors told each provider where they detected prompt caching before publishing, and to their knowledge at least five made changes to mitigate vulnerabilities.

### A real incident

OpenAI's write-up of the 20 March 2023 outage gives the cause as a bug in the open-source redis-py library. OpenAI used Redis to cache user information over a shared pool of connections. A request cancelled after it was pushed to the queue but before its response was popped left a connection out of step, and the next response on it could be data left behind for someone else. Usually that gave an error, but sometimes the data matched the expected type, "so what gets returned from the cache appears valid, even if it belongs to another user."

OpenAI said a change on its server at 1 a.m. Pacific on March 20 caused a spike in cancellations. Some users could see titles from another active user's chat history, and the first message of a new conversation may have been visible to another user. The same bug may have shown first and last name, email address, payment address, card type, last four digits and expiry date for 1.2% of ChatGPT Plus subscribers active during a nine-hour window; full card numbers were not exposed. OpenAI said it believed the number of users whose data was actually revealed to someone else was "extremely low". OpenAI took the service offline, patched the bug and added redundant checks that the data returned from its cache matches the requesting user.

### What providers and servers say

These pages were read on 2026-10-19 and change, so check the live page. These are documentation claims, not audit results. OpenAI's prompt caching guide says "Caches are not shared across organizations" and, for GPT-5.6 and later, describes `prompt_cache_key` as a way to keep separate cache accounting for customers, users or workspaces, adding that it "also helps prevent cache-hit probing across customers". Anthropic's documentation says "Caches are isolated between organizations" and, on some platforms, per workspace. The Anthropic page describes no per-user setting. The vLLM server documents an optional per-request `cache_salt`, mixed into the short code made from the first block, so that only requests with the same salt reuse cached blocks. Its page says this prevents timing-based attacks.

### Proof it works

`test_leak_sim.py` pins the counts above: 14, 10, 6 and 71% with the shared key, and 14, 5, 0 and 36% with the user in it. It runs 300 random requests to check that every leak carries another user's order code or street and that opening hours never leaks. It runs 40 random sequences to check that the user in the key leaves no leak and never gives more hits. It checks that a stored start is reused whole or not at all, and only under the same salt. It checks that only the right guess is quick without the user in the key, and that all three wait the same with it. It pins the noise: 68% and 99% shared, 33% and 33% salted, the same on a second run. It also rebuilds the numbers in the text, the tables and the figures from the code's results, so a number that drifts fails the test. `test_widget.py` runs the widget's JavaScript in Node against the Python in both modes, and drives the page through a small stand-in for the browser, checking the text, the bar widths and the line a screen reader announces.

## Skills this lesson builds

- Explain why a cache keyed by the question alone can hand one user's answer to another.
- Say what does not leak (the model's weights) and the three places a leak does come from.
- Put the user in a cache key, and count what it costs in hits.
- Read a timing signal from noisy waits, and say why many tries and a salt matter.
- Test an app with two accounts that ask the same question.

**Portfolio project:** build a small help bot with a response cache whose key can be the question alone or the user plus the question. Write a test with three users and a fixed list of requests that counts hits and wrong answers in both modes. Publish the code, the table of counts and a note on which answers you let share a key and why.

## Check yourself

1. A help bot caches whole answers under the question text alone. User B asks "Where is my order?" right after User A. What goes wrong, and what is the fix?

   <details><summary>Answer</summary>The cache finds A's stored answer under the same key and hands it to B, so B sees A's order. The fix is to put the user in the key, so that each user finds only their own entries.</details>

2. Does the model itself carry one user's chat into another's?

   <details><summary>Answer</summary>Not usually. Its weights do not change while it answers, and it keeps nothing between replies. Leaks come from the software around it: a shared cache key, logs that keep full text, or mixed-up requests.</details>

3. In the toy, the hit rate falls from 71% to 36% when the user goes in the key. Why is that not a loss of right answers?

   <details><summary>Answer</summary>Six of the shared key's 10 hits were leaks, so only 4 were right. The user key has 5 hits and all are right: it gives up one harmless hit (request 4) and wins two (requests 12 and 14).</details>

4. With the shared key, one try per guess finds the real street 68% of the time and the average of 25 finds it 99%. With the user in the key both give 33%. Why?

   <details><summary>Answer</summary>The real guess waits 44.6 ms less, but the random delay of about 60 ms hides that in one try. Averaging 25 waits cuts the noise to about 12 ms, so the gap shows. With the user in the key there is no gap to find, so more tries change nothing and the result is a blind guess, one in three.</details>

5. OpenAI said it added "redundant checks" that data from its cache matches the requesting user. What would such a check catch?

   <details><summary>Answer</summary>A response that is valid in form but belongs to another user, as happened when a cancelled request left a connection out of step. The check compares the owner of the data with the user who asked, and refuses to show data for the wrong user.</details>

## Further reading

Web pages were read on 2026-10-19.

**Gentle**

- [Why does a chatbot forget?](https://spicyshark.co/learn/why-chatbots-forget/#the-model-keeps-nothing-between-replies) (lesson 3, why the model keeps nothing between replies)
- [What is prompt caching, and when does it save money?](https://spicyshark.co/learn/what-is-prompt-caching/#what-the-server-keeps) (lesson 8, the stored start whose reuse is shortened by a hit)
- [Why does an AI wait a moment before the first word?](https://spicyshark.co/learn/why-ai-waits-before-the-first-word/#two-numbers-to-watch) (lesson 23, the wait that a hit shortens)
- [March 20 ChatGPT outage: Here's what happened](https://openai.com/index/march-20-chatgpt-outage/) (OpenAI, 24 March 2023; the incident, its cause and what was visible)

**Deep**

- [Auditing Prompt Caching in Language Model APIs](https://arxiv.org/abs/2502.07776) (Gu, Li, Kuditipudi, Liang and Hashimoto, 2025; timing audits and the case for per-user caching)
- [Prompt caching](https://developers.openai.com/api/docs/guides/prompt-caching) (OpenAI; caches not shared across organizations, and `prompt_cache_key`)
- [Prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) (Anthropic; "Cache storage and sharing", isolation between organizations and workspaces)
- [Automatic Prefix Caching](https://docs.vllm.ai/en/latest/design/prefix_caching.html) (vLLM design page; "Cache Isolation for Security" and the per-request `cache_salt`)
