Skip to content

fix: preserve multiple set-cookie headers#60

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

fix: preserve multiple set-cookie headers#60
marioparaschiv wants to merge 3 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
    • Enhanced cookie handling to preserve all cookies when processing response headers, fixing issues where multiple cookies could be lost during header operations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 5, 2026

Warning

Rate limit exceeded

@marioparaschiv has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 5 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 22 minutes and 5 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7018d10e-73f1-4bfd-a41b-a97e2ffdf7e0

📥 Commits

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

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

Walkthrough

The mergeHeaders function in src/utils.ts now builds merged headers by iterating responseHeaders.entries() (skipping set-cookie) and appending Set-Cookie values from responseHeaders.getSetCookie(). It also supports array-valued set-cookie on plain setHeaders objects, preserving multiple cookie entries.

Changes

Cohort / File(s) Summary
Header Merge Logic Refinement
src/utils.ts
Rewrote mergeHeaders to avoid Object.fromEntries() for response headers, explicitly skip set-cookie during initial copy, append all Set-Cookie values from responseHeaders.getSetCookie(), and handle set-cookie from setHeaders when it's a Headers instance or a plain object (supports array or string values). Response headers still take precedence.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

♡ Cookies refused to be squashed by objects, nee~ ( ̄▽ ̄)♡
Now each Set-Cookie gets its little spotlight, yay~ (≧▽≦)♡
Iteration came, collapsed strings went bye-bye~ (^▽^)♡
Headers stand tall, uncompromised and sly~ (¬‿¬)♡

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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 by preserving them during header merging.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

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

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