Quick Answer — On March 24, 2023, OpenAI disclosed that a bug in an open-source Redis client library (redis-py’s async version) — combined with a misconfigured cache key prefix — had, for a ~9-hour window on March 20, let some ChatGPT users see fragments of other users’ chat histories: titles from strangers’ conversations, and for a subset of ChatGPT Plus subscribers, payment-related data (name, email, payment address, last four digits + expiry of the card) in the billing UI. Roughly 1.2% of hourly-active users were exposed to the chat-title leakage per the post-mortem. The lesson: a one-line client-library cancellation bug plus a disabled safety check equals cross-tenant disclosure at platform scale — shared caches inherit multi-tenancy’s blast radius.
What happened
- Date of bug window: March 20, 2023, ~UTC early hours. A specific sequence — cancel a running request — triggered a redis-py async bug that could leave a connection with corrupted/poisoned state returning someone else’s cache entries.
- The config amplifier: A key-prefix setting meant to isolate tenants per-user was mistakenly disabled during a debugging session days earlier, so the poisoned connection could read across users’ keys — that’s the single-tenant-no-more moment.
- What leaked: (a) chat-titles visible in some users’ history sidebars that weren’t theirs; (b) during the same window, ~1.2% of ChatGPT Plus subscribers had another user’s name, email, payment address, card last-four and expiry surface in their own billing pop-up; (c) a small set saw first-message snippets.
- The response: OpenAI took ChatGPT offline mid-launch-week, patched the redis-py bug upstream (a contributed fix), audited logs, notified affected users and regulators, and published one of the more candid post-mortems of the AI era.
Why a client-library bug became a cross-tenant leak
| Layer | Failure mode |
|---|---|
| redis-py async cancellation bug | Aborted requests leave connections in corrupted state; subsequent reads fetch wrong keys’ data |
| Disabled key prefix | Removed the per-user namespace that would have contained the poisoned reads |
| Shared cache topology | All tenants’ hot data in one logical cluster — one bad connection reaches everyone |
| No cross-tenant canary | Nothing asserted “user A must never see user B’s keys” as an invariant test |
| UI trust in cache | Frontend rendered cache returns verbatim into sidebars and billing widgets |
The disclosure pattern worth copying
Three things separated OpenAI’s handling from the standard breach-notice boilerplate. First, specific math: the 1.2%-of-hourly-active framing gave users and reporters an honest denominator instead of vague “a small number.” Second, technical candor: naming the exact library, the cancellation path, and the config mistake let every other platform on redis-py self-audit within days — the fix rippled far past OpenAI. Third, visible remediation: the sidebar came off, the service went dark during peak launch-week hype, and the bug bounty followed. The cost of that transparency was low; the trust yield was lasting.
Timeline
| Date | Event |
|---|---|
| 2023-03 ~15 | Debugging change disables cache-key prefix isolation (per post-mortem timeline) |
| 2023-03-20 | redis-py bug window: canceled requests → poisoned connections → cross-user reads, ~9 hours |
| 2023-03-20 → 21 | ChatGPT intermittently unavailable; history sidebar disabled (the visible symptom) |
| 2023-03-24 | Disclosure + post-mortem: 1.2% chat-title exposure; billing-data subset; redis-py patch details |
| 2023-04 → 05 | OpenAI ships bug bounty, trust-boundary reviews; privacy regulators absorb the incident into AI-governance debates |
Defensive lessons
- Cancellation is a correctness edge. Async request-abort paths corrupt state in subtle ways; chaos-style testing that cancels requests at random moments is how you find them before strangers’ data does.
- Isolation flags are load-bearing. The prefix-disable was a debug convenience that production paid for. Any isolation control that can be toggled for debugging will eventually ship off — config-gate it, alarm on it, make re-enabling mandatory pre-deploy.
- Tenant-boundary invariant tests. “No query path may return rows/keys not owned by the requester” is a testable property; write it as an always-on suite for every cache and store fronting multi-tenant UIs.
- Credit-card fields: surface less. Last-four + expiry is ‘safe’ only by convention; a billing widget that receives it unprompted is a disclosure waiting for a rendering bug. Fetch payment data on explicit action only.
- Post-mortems build trust. OpenAI’s engineering-detail disclosure became the template AI platforms are measured against — honesty about the 1.2% math, the exact bug, and the config error.
Why it still matters in 2026
This incident is the founding case study for multi-tenant data planes under AI workloads: conversations are intimate data, chat UIs render server state eagerly, and the caching layers that make platforms responsive inherit every tenancy risk databases spent decades walling off. In 2026 — RAM-hot vector stores, shared prompt caches, and agent memory systems all humming on similar open-source plumbing — the redis-py episode reads as prophecy: open-source dependencies plus hasty config equal cross-tenant breach, and nobody audits the cancel path. The platforms that survived the subsequent rounds were those that adopted tenant-invariant tests and config-drift alarms as policy. Everyone else became a future post-mortem.
Was any full conversation content exposed?
Per the post-mortem: chat titles (sidebar fragments) for the ~1.2%, plus first messages for a much smaller set where a title was absent and the UI fell back to message text. The billing-data exposure was its own subset. Full conversation bodies were not in the leaked surface — but a title alone can be plenty: they’re written like search queries and confessions.
Why did the sidebar show strangers’ chats before the billing data did?
Because the sidebar was the highest-frequency reader of the cache: every page load fetched the user’s chat list, so poisoned connections manifested there first. The billing widget surfaced data only when users opened it — meaning some affected Plus subscribers may never have looked at the stranger’s card details that were technically delivered to their browser. Client-visible-but-unrendered is still a reportable disclosure, which the post-mortem correctly treated it as.
What happened to redis-py?
The maintainers patched the cancellation-flush bug family across affected releases, and OpenAI contributed upstream fixes and detail — a quiet landmark in vendors-take-open-source-seriously arc. The deeper lesson stuck with platform engineers: the client library between your app and your cache is security-critical code, even though it never touches a password.
Part of the hmmnm.com security-timeline series — one event per month, 2021–2024, indexed here.
