Skip to content

fix(server): clearCookies({name}) should not transiently delete other cookies#40955

Open
adityasingh2400 wants to merge 6 commits into
microsoft:mainfrom
adityasingh2400:fix-clear-cookies-transient-delete-40953
Open

fix(server): clearCookies({name}) should not transiently delete other cookies#40955
adityasingh2400 wants to merge 6 commits into
microsoft:mainfrom
adityasingh2400:fix-clear-cookies-transient-delete-40953

Conversation

@adityasingh2400
Copy link
Copy Markdown
Contributor

Fixes #40953.

BrowserContext.clearCookies(options) currently wipes every cookie via doClearCookies() and then re-adds the ones that did not match the filter. Pages that subscribe to the cookieStore.change API observe a transient deletion of the kept cookies during the gap between the wipe and the readd, which is enough to trip route-guards, useSyncExternalStore-style auth state machines, and similar listeners. With cookieStore now Baseline 2025, this race window is observable from user code.

When a filter (name, domain, or path) is set, this PR expires only the matching cookies in place by calling addCookies with expires: 0; the no-filter path still delegates to doClearCookies() as before, so no per-browser code is touched.

Credit to @jasikpark for the full root-cause analysis and the proposed fix shape in the issue.

Added a Chromium-only test in tests/library/browsercontext-clearcookies.spec.ts that adds two cookies, subscribes to cookieStore.change via the page, then calls clearCookies({ name: 'delete_me' }) and asserts the kept cookie never appears in a deletion event.

… cookies

BrowserContext.clearCookies(options) currently wipes every cookie via
doClearCookies() and then re-adds the ones that did not match the
filter. Pages that subscribe to cookieStore.change observe a transient
deletion of the kept cookies during the gap between the wipe and the
readd, which is enough to trip route-guards, useSyncExternalStore-style
auth state machines, and similar.

When a filter (name, domain, or path) is set, expire only the matching
cookies in place by calling addCookies with expires=0; the no-filter
path still delegates to doClearCookies() as before. No per-browser code
is changed.

Reported and diagnosed by @jasikpark in microsoft#40953.
@adityasingh2400
Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@adityasingh2400
Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

Skn0tt added 3 commits May 26, 2026 15:09
…ned cookies

Adds coverage for two cases the new filtered-clearCookies path in microsoft#40955
does not handle: __Secure- prefixed cookies (rejected by Chromium because
the expire payload omits secure:true) and partitioned (CHIPS) cookies
(silently survive because partitionKey is dropped from the expire payload).
Same root cause as the __Secure- regression: without secure/sameSite in
the expire payload, Chromium rejects the cookie with "Invalid cookie
fields".
…arCookies

Spread the original cookie into the expire payload instead of picking
name/domain/path. Without secure/sameSite/partitionKey/_crHasCrossSiteAncestor,
Chromium rejects __Secure- and __Host- prefixed cookies with "Invalid cookie
fields" and silently leaves partitioned (CHIPS) cookies in place.
@Skn0tt
Copy link
Copy Markdown
Member

Skn0tt commented May 26, 2026

Code looked good, I found some regressions that I covered with tests + fixed. @yury-s could you take a look since you last touched Cookie around CHIPS?

@Skn0tt Skn0tt requested a review from yury-s May 26, 2026 13:22
@adityasingh2400
Copy link
Copy Markdown
Contributor Author

Thanks for catching those regressions and covering them with tests, much appreciated. Looks good to me.

await page.goto(server.PREFIX);

const eventsHandle = await page.evaluateHandle(() => {
const events: { kind: string, name: string }[] = [];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only delete cookies are changed, no need to store kind

it.describe('clearCookies with filter preserves cookie identity', () => {
it.use({ ignoreHTTPSErrors: true });

it('should remove __Secure- prefixed cookies by name', async ({ context, httpsServer }) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop these *-prefixed tests

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see that @Skn0tt added them later. @Skn0tt do you think we need keep these tests for some reason, they look unrelated to the change?

expect(await page.evaluate('document.cookie')).toBe('keep_me=1');
});

it.describe('clearCookies with filter preserves cookie identity', () => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the title doesn't seem to match the tests

it('should remove partitioned cookies by name', async ({ context, httpsServer, browserName }) => {
it.skip(browserName !== 'chromium', 'Partitioned cookies (CHIPS) are Chromium-specific');
await context.addCookies([
{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also add another cookie that is preserved?

@adityasingh2400
Copy link
Copy Markdown
Contributor Author

Thanks for the review. Happy to make these: drop storing the kind at line 187 where only deletions change, fix the title at line 204 so it matches what the test actually does, and add a preserved cookie alongside the deleted one at line 232. The star-prefixed tests at line 207 came in through Skn0tt's commits, so I will leave those to whatever you and Skn0tt land on and then push the rest in one go, to avoid clobbering his changes.

@github-actions
Copy link
Copy Markdown
Contributor

Test results for "MCP"

7181 passed, 1113 skipped


Merge workflow run.

@github-actions
Copy link
Copy Markdown
Contributor

Test results for "tests 1"

2 flaky ⚠️ [chromium-library] › library/video.spec.ts:682 › screencast › should capture full viewport on hidpi `@chromium-ubuntu-22.04-node24`
⚠️ [playwright-test] › ui-mode-trace.spec.ts:185 › should show snapshots for steps `@windows-latest-node20`

43952 passed, 863 skipped


Merge workflow run.

@Skn0tt
Copy link
Copy Markdown
Member

Skn0tt commented May 27, 2026

@yury-s those tests caught regression in the original change, I can drop them now. Since your feedback is about tests and not impl, I assume you don't see a problem with deleting via expires: 0.

Skn0tt added 2 commits May 27, 2026 16:34
- Drop *-prefixed tests (__Secure-, __Host-)
- Simplify partitioned-cookie test: inline newContext, add a preserved cookie
- Simplify event log to plain strings for direct equality assertion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: context.clearCookies({name}) transiently deletes non-matching cookies (observable via cookieStore.change)

3 participants