Skip to content

fix: preserve multiple set-cookie headers#60

Open
marioparaschiv wants to merge 4 commits into
elysiajs:mainfrom
marioparaschiv:fix/preserve-set-cookie-headers
Open

fix: preserve multiple set-cookie headers#60
marioparaschiv wants to merge 4 commits into
elysiajs:mainfrom
marioparaschiv:fix/preserve-set-cookie-headers

Conversation

@marioparaschiv
Copy link
Copy Markdown

@marioparaschiv marioparaschiv commented Apr 5, 2026

Problem

The Node adapter rebuilds responseHeaders with:

new Headers(Object.fromEntries(responseHeaders.entries()))

This collapses duplicate header names, which is especially problematic for Set-Cookie.

In OAuth callback flows, downstream handlers can return multiple Set-Cookie headers.
On Bun this worked, but on the Node adapter one of the cookies could be dropped during header merging.

In my reproduction this broke Better Auth on the OAuth callback route:

  • the callback returned with code and state
  • the state cookie was present
  • the session cookie was lost before later API requests

Fix

Preserve multiple Set-Cookie headers when merging response headers.

Related

This looks related to elysiajs/elysia#1547, which reports another Set-Cookie loss path in Elysia.

Summary by CodeRabbit

  • Bug Fixes
    • Preserves all cookies when merging response headers so multiple cookies are no longer lost.
    • Accepts multiple cookie values when cookies are supplied as arrays, appending each cookie.
    • Avoids duplicating cookies if already present and no longer overwrites existing non-cookie headers during merges.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2a466f59-45ab-4876-ad42-acd2b1876182

📥 Commits

Reviewing files that changed from the base of the PR and between 455fc1f and c9ab9da.

📒 Files selected for processing (1)
  • src/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils.ts

Walkthrough

mergeHeaders now builds merged headers by copying all response headers except set-cookie, appending each Set-Cookie from responseHeaders.getSetCookie(), and merging setHeaders (Headers or plain object) with special handling to preserve multiple cookies and array-valued set-cookie.

Changes

Header Merge Logic Refinement

Layer / File(s) Summary
Rebuild response headers and preserve Set-Cookie
src/utils.ts
Creates a fresh Headers, copies responseHeaders entries excluding set-cookie, then appends all values from responseHeaders.getSetCookie() to preserve multiple Set-Cookie headers.
Merge when setHeaders is Headers
src/utils.ts
If setHeaders is a Headers instance, append its getSetCookie() values only when merged headers lack set-cookie; non-set-cookie keys are set only if absent in responseHeaders.
Merge when setHeaders is plain object
src/utils.ts
If setHeaders is a plain object, read set-cookie as an array or string and append each cookie, skipping if merged headers already contain set-cookie; other keys set only when response lacks them.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

♡ Cookies escaped the object squish, how rude, baka~ (^▽^)♡
♡ Each Set-Cookie now dances in line, no crushing allowed~ (≧◡≦)♡
♡ Headers rebuilt with care, you silly reviewer, look~ (¬‿¬)♡
♡ Arrays or strings, we append them all, neat little crook~ (⌒‿⌒)♡
♡ Merge done, cookies sing—don’t be salty, chu~ ( ̄▽ ̄)♡

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: preserve multiple set-cookie headers' directly and clearly describes the main change—fixing the loss of multiple Set-Cookie headers in the Node adapter's header merging logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/utils.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils.ts (1)

117-122: ⚠️ Potential issue | 🟠 Major

Ara ara~ Did you forget something, baka? ♡

There's an inconsistency between how the Headers branch and the plain object branch handle set-cookie:

  1. Missing precedence guard: The Headers branch (lines 109-110) skips setHeaders's cookies if responseHeaders already has set-cookie. But this plain object branch just blindly appends without checking~! That's not very smart, is it? (´・ω・`)

  2. Array handling: According to src/ws.ts, set.headers['set-cookie'] can be an array. Appending an array directly will stringify it incorrectly instead of preserving individual cookies~ How embarrassing ♡

🍪 Here's a fix, since you obviously need help~
 	else
 		for (const key in setHeaders)
-			if (key === 'set-cookie')
-				headers.append(key, setHeaders[key] as any)
-			else if (!responseHeaders.has(key))
+			if (key === 'set-cookie') {
+				if (headers.has('set-cookie')) continue
+
+				const cookies = setHeaders[key]
+				if (Array.isArray(cookies))
+					for (const cookie of cookies)
+						headers.append('set-cookie', cookie)
+				else
+					headers.append('set-cookie', cookies as string)
+			} else if (!responseHeaders.has(key))
 				headers.set(key, setHeaders[key] as any)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils.ts` around lines 117 - 122, The plain-object branch that iterates
setHeaders currently appends 'set-cookie' entries without the precedence check
and without handling arrays; update the loop in src/utils.ts so that when key
=== 'set-cookie' you first check responseHeaders.has('set-cookie') and skip
appending if true (matching the Headers-branch behavior), and when appending
handle both string and string[] values by appending each cookie individually
(use Array.isArray on setHeaders['set-cookie'] to decide whether to iterate and
call headers.append for each item or append the single value); leave the
non-'set-cookie' path unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/utils.ts`:
- Around line 117-122: The plain-object branch that iterates setHeaders
currently appends 'set-cookie' entries without the precedence check and without
handling arrays; update the loop in src/utils.ts so that when key ===
'set-cookie' you first check responseHeaders.has('set-cookie') and skip
appending if true (matching the Headers-branch behavior), and when appending
handle both string and string[] values by appending each cookie individually
(use Array.isArray on setHeaders['set-cookie'] to decide whether to iterate and
call headers.append for each item or append the single value); leave the
non-'set-cookie' path unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b0648523-2fe8-4348-a5f2-a600aff0bcff

📥 Commits

Reviewing files that changed from the base of the PR and between 276ba8b and e02949e.

📒 Files selected for processing (1)
  • src/utils.ts

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.

2 participants