Skip to content

fix: deduplicate Set-Cookie headers from middleware + server actions#92727

Open
claygeo wants to merge 1 commit intovercel:canaryfrom
claygeo:fix/deduplicate-middleware-set-cookie
Open

fix: deduplicate Set-Cookie headers from middleware + server actions#92727
claygeo wants to merge 1 commit intovercel:canaryfrom
claygeo:fix/deduplicate-middleware-set-cookie

Conversation

@claygeo
Copy link
Copy Markdown

@claygeo claygeo commented Apr 13, 2026

Summary

Fixes #69785

When middleware and a server action both set the same cookie (e.g., shared-cookie), the response contains duplicate Set-Cookie headers for that cookie name. This violates RFC 6265 Section 4.1.2 and can cause 502 errors when response headers exceed proxy limits (8KB on AWS ALB/Akamai).

Root Cause

Middleware cookies flow through two independent paths to the final response:

  1. Path A (onUpdateCookies via mergeMiddlewareCookies): When the page/action calls cookies(), middleware cookies are merged into the mutable cookie store, and res.setHeader('Set-Cookie', ...) replaces all Set-Cookie headers (correct, deduplicates)
  2. Path B (send-response.ts appendHeader): The Response object's headers (which also contain middleware cookies via appendMutableCookies) are appended to res, creating duplicates of what Path A already wrote

Fix

After copying all response headers to res in send-response.ts, deduplicate Set-Cookie headers by cookie name. The last value for each name wins (server action over middleware), using a simple Map<string, string> keyed by cookie.split('=')[0].

Changes

  • packages/next/src/server/send-response.ts: Add Set-Cookie dedup block after header copying loop
  • test/e2e/app-dir/app-middleware/: Add regression test with middleware + server action setting overlapping cookies

Test Plan

  • New test: middleware cookies visible on initial load (/cookie-dedup)
  • New test: no duplicate cookie names in Set-Cookie response headers
  • New test: x-middleware-set-cookie internal header not leaked
  • Existing middleware cookie tests still pass (rsc-cookies, cookie-options, etc.)

…s set the same cookie

When middleware sets a cookie and a server action also sets the same cookie,
the response contained duplicate Set-Cookie headers for the same cookie name.
This violated RFC 6265 and could cause 502 errors when response headers
exceeded proxy limits (8KB on AWS/Akamai).

The fix deduplicates Set-Cookie headers by cookie name in send-response.ts,
which is the final gateway before the response is sent. The last value for
each cookie name wins, preserving the correct execution order (server action
after middleware).

Fixes vercel#69785
@nextjs-bot
Copy link
Copy Markdown
Collaborator

Allow CI Workflow Run

  • approve CI run for commit: 2ac1deb

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

1 similar comment
@nextjs-bot
Copy link
Copy Markdown
Collaborator

Allow CI Workflow Run

  • approve CI run for commit: 2ac1deb

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

Comment on lines +73 to +77
const existingSetCookie = res.getHeader('set-cookie')
if (existingSetCookie) {
const cookieArray = Array.isArray(existingSetCookie)
? (existingSetCookie as string[])
: [String(existingSetCookie)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
const existingSetCookie = res.getHeader('set-cookie')
if (existingSetCookie) {
const cookieArray = Array.isArray(existingSetCookie)
? (existingSetCookie as string[])
: [String(existingSetCookie)]
const existingSetCookie = res.getHeaderValues('set-cookie')
if (existingSetCookie) {
const cookieArray = existingSetCookie

Set-Cookie deduplication code uses res.getHeader() which returns a comma-joined string, making the deduplication logic completely ineffective.

Fix on Vercel

await browser.deleteCookies()
})

it('should not produce duplicate Set-Cookie headers when middleware and server action set the same cookie', async () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The "cookie dedup" tests never invoke the server action, so they cannot validate the Set-Cookie deduplication behavior they claim to test.

Fix on Vercel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setting a cookie in middleware & a server action results in duplicate Set-Cookie headers

2 participants