Breach report
The ChatGPT Redis bug: a cancelled request handed you a stranger's chat history
What happened
On March 20, 2023, ChatGPT users began seeing brief descriptions of other users' conversations in their chat-history sidebar. OpenAI took the service offline to investigate. The company later confirmed that during a roughly nine-hour window (1–10 a.m. PT), a bug also may have exposed payment-related information belonging to 1.2% of ChatGPT Plus subscribers who were active in that period. The exposed billing fields included another active user's first and last name, email address, payment address, credit-card expiration date, and the last four digits only of a card number — full card numbers were not exposed.
Root cause
The leak came from the open-source redis-py client OpenAI used to cache user information. When a request is cancelled, the connection can be returned to the pool in a corrupted state. Under a specific race condition, a subsequent request reusing that pooled connection could receive data left over from the cancelled one — data belonging to a different user. A separate change earlier that morning increased request cancellations, which raised the rate of the collision until it surfaced as a visible cross-user leak. This is a classic concurrency defect (CWE-362): shared state — the connection pool — with no per-request identity check on what came back.
How it would have been caught
An integration test that fires overlapping requests from two distinct users and cancels some mid-flight, then asserts that every response's payload belongs to the requesting user, reproduces the fault directly. So would a check that validates the identity stamped on any cache read matches the caller before the data is used. The failure only appears under concurrency, so a single-user happy-path test can never see it — you must drive contention.
How to prevent it
- Never trust that data returned from a shared cache or pooled connection belongs to the caller. Stamp cache entries with the owner's identity and verify it on read.
- Treat connection pools as shared mutable state: ensure a cancelled or errored request cannot return a dirty connection to the pool.
- Load-test with concurrent, cancelling clients — the bugs that matter here are invisible without contention.
The Breachwire test (red → green)
Drive two authenticated users concurrently through the caching path, cancelling a fraction of requests, and assert a response is returned that contains User B's data while User A is the caller — the RED proof that pooled state crosses the user boundary. Add the owner-identity check on every cache read (and fix the pool's cancel handling), then re-run the same concurrency load and confirm every response matches its own caller, with no cross-user payload across thousands of cancelled requests.