From a116d4c5c12619dc54e2ccd8c3127560d750f931 Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Wed, 12 Aug 2026 19:43:18 +0200 Subject: [PATCH 1/2] Clear the OAuth popup result from localStorage after handing it over MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The callback page writes its result to localStorage as the reliable same-origin completion channel, and the opener removes it on pickup. When nothing is listening — an abandoned flow, a reloaded opener, any caller not using the React helper — nobody ever removes it, and the payload carries the identity label (an email) and, on failure, the error preview. It sat in the user's browser profile indefinitely. The page now clears its own entry: on success just before the existing auto-close, and on failure after a delay, since a failed flow deliberately keeps the window up. This cannot cost a listener the result — a `storage` event captures `newValue` at dispatch, so an opener that was notified already holds it, and the only other reader polls `popup.closed`, not storage. The tests RUN the generated script against stub globals rather than matching its source, because a string assertion passes just as well on a script that never executes, and what is at stake here is what the browser is left holding. --- packages/core/api/src/oauth-popup.test.ts | 74 +++++++++++++++++++++++ packages/core/api/src/oauth-popup.ts | 9 ++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/packages/core/api/src/oauth-popup.test.ts b/packages/core/api/src/oauth-popup.test.ts index 12efa6971b..77278a2fa0 100644 --- a/packages/core/api/src/oauth-popup.test.ts +++ b/packages/core/api/src/oauth-popup.test.ts @@ -149,6 +149,80 @@ describe("popupDocument", () => { expect(script).toContain("channel\\u003c/script\\u003e"); }); + // The assertions below RUN the generated script against stub globals rather + // than matching its source text. A string check would pass on a script that + // never executes — and the property at stake here is what the browser is left + // holding, which only running it can show. + const runPopupScript = (html: string) => { + const script = / `; }; From 2e12352ebefbf2af322758b073b346d60021ac59 Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Wed, 12 Aug 2026 22:56:39 +0200 Subject: [PATCH 2/2] Add a changeset for the popup storage-residue fix --- .changeset/oauth-popup-localstorage-residue.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/oauth-popup-localstorage-residue.md diff --git a/.changeset/oauth-popup-localstorage-residue.md b/.changeset/oauth-popup-localstorage-residue.md new file mode 100644 index 0000000000..443f6ffa58 --- /dev/null +++ b/.changeset/oauth-popup-localstorage-residue.md @@ -0,0 +1,9 @@ +--- +"executor": patch +--- + +**The OAuth popup clears its result out of `localStorage` after handing it over** + +The popup writes its result to `localStorage` as the fallback completion channel, because `postMessage` is severed when a provider's consent page sets COOP and `BroadcastChannel` can be partitioned or raced by the auto-close. Nothing removed that entry afterwards, so the payload — which carries the identity label, an email, and on failure the error preview — stayed parked in the user's browser profile. + +The entry is now cleared once the handover has had time to land. This cannot cost a listener the result: a `storage` event captures `newValue` at dispatch, so an opener that has been notified already holds it.