Your AI chat widget is showing the last user's conversation
The short answer
Because the transcript lives in the browser and the widget changed identity without clearing it. Permissions can hold perfectly, with every new request refused under the new user's token, while the previous user's messages sit on screen. Clear the conversation whenever the signed-in subject changes, comparing the user id rather than the token string, and detect sign-out by watching for the identity to disappear instead of relying on one sign-out button.
Here is a bug report from a real integration test. A tester signed in as one user and had a long session with the assistant, the kind where it looks up that person’s bookings and names them. They signed out, signed in as a second user in the same browser, and opened the panel. The first user’s whole conversation was on screen, with their data in it. Reloading the page did not clear it.
The interesting part is what did not leak. The tester tried a write as the second user and it was refused, correctly, under the second user’s own token. Permissions held completely. What leaked was the history.
Why permissions can hold while the conversation leaks
A chat widget keeps its transcript in the browser so a reload or a page change does not lose the conversation. That transcript is rendered from local storage, with no round trip to any server and therefore no permission check. The identity changed, the widget swapped in the new user’s token, and it left the rendered conversation exactly where it was.
Worse, the fix already existed. A function to forget local history on sign-out was written, documented as preventing exactly this, and never called. Code that describes the leak it is not preventing is worse than no code, because anyone reading it believes the case is handled.
Clear on a change of subject, never on a change of token
The obvious fix is to clear the conversation whenever the token changes. That fix is its own bug. Tokens are short-lived and refreshed every few minutes for the same person sitting still, so comparing token strings wipes their conversation mid-task, over and over.
Compare the subject instead, the user id inside the token, and apply two rules:
- A different subject clears. Different person, so the previous person’s conversation must not stay on screen.
- Anonymous to signed in does not. Somebody browsing and then signing in is the same human continuing. Wiping what they just asked would be a bug of your own making.
Say that it happened
A silent wipe is correct and still confusing. In one demo someone switched accounts to show a permission difference, switched back mid-conversation, and answered a question the assistant had asked. The panel had emptied, the in-flight turn had been cut, and nothing on screen said why the assistant was asking the same question again.
Show a notice after clearing, and if a task was interrupted, say that nothing was carried out: you signed in as a different user, so I stopped and cleared the conversation. Nothing was carried out. The notice goes in after the wipe, because the wipe empties the place it is written to.
There is rarely one sign-out button
The standard advice is to call the widget’s reset wherever your app signs someone out. One audited app did that from more than ten places: the account menu, password change, two-factor setup, onboarding, email verification, each routing differently. There was no single line to add it to.
What worked was watching for the identity going away. When the page regains focus or becomes visible, ask your token endpoint who is signed in; if the answer is nobody, reset. That covers every sign-out path at once, and a case the standard advice misses entirely: a session that simply expired, where nothing ever signed out.
It also depends on your token endpoint telling the truth about “nobody”, which is the subject of telling an embedded assistant who is signed in. A failed request that reports signed out turns this protection into a wipe on every network blip.
Common questions
If the new user's requests are refused, what actually leaked?
The rendered history. Stored transcripts are shown from local storage without a round trip, so a second user can read the first user's questions and whatever data the assistant showed in its answers, even though every new action runs under their own permissions.
Why not clear the chat whenever the token changes?
Because tokens are short-lived and refreshed every few minutes for the same person. Comparing token strings would wipe someone's conversation mid-task, repeatedly. Compare the subject, the user id inside it, which is the only part that says who.
Where should an app call reset on sign-out?
Ideally one place, and most real apps do not have one: one audited app signed users out from more than ten different code paths. Watching for the identity going away, on focus and visibility change, covers all of them, and also covers a session that simply expired.
Keep reading
- How to tell an embedded AI assistant who is signed in
Why the assistant cannot see your session, why it needs a function and not a token, and the encoding detail that fails every signature silently.
- Postgres row-level security for a multi-tenant AI agent
The isolation boundary, the tables that deliberately sit outside it, and why the loud failure is the one worth engineering for.
- Your coding agent can set up your product’s AI agent now
The setup that used to mean a dashboard, three tabs and a key to paste is now one sentence to the coding agent you already have open.
Verb is this, built. An AI assistant you embed in your SaaS with one script tag: it calls your own API as the signed-in user, confirms before it changes anything, and logs every action. Free to build and test.