[GH-ISSUE #295] bug: Set-Cookie headers flattened in prod-server.ts response merging #71

Closed
opened 2026-05-06 12:36:59 +02:00 by BreizhHardware · 0 comments

Originally created by @southpolesteve on GitHub (Mar 6, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/295

Problem

In prod-server.ts around line 944-945, middleware headers are merged into the final response using a Record<string, string> pattern:

const responseHeaders: Record<string, string> = { ...middlewareHeaders };
response.headers.forEach((v, k) => { responseHeaders[k] = v; });

Headers.forEach() iterates Set-Cookie values as combined comma-separated strings (via Headers.get() semantics), or as individual entries depending on the runtime. Either way, the Record<string, string> target loses multiple Set-Cookie values:

  • If forEach yields per-entry, the last Set-Cookie wins (previous ones overwritten)
  • If forEach yields the comma-joined string, the result is a single corrupted Set-Cookie (cookie values with Expires= dates contain commas, so comma-joining breaks parsing)

The resulting sendCompressed call passes this flattened object to writeHead, which can't reconstruct the original multiple Set-Cookie headers.

Context

This is a pre-existing bug, surfaced during review of #281 (which correctly uses responseHeaders.append("set-cookie", ...) in renderPage). Now that gSSP can set cookies via res.setHeader("set-cookie", ...), this flattening in prod-server.ts becomes more likely to hit in practice.

Fix

Use Headers.getSetCookie() (available in Node 20+, Cloudflare Workers, Deno) to preserve array-valued Set-Cookie headers, or switch responseHeaders to use the Headers API throughout instead of a plain object.

Discovered by

Flagged by bigbonk in https://github.com/cloudflare/vinext/pull/281#issuecomment-4009074461

Originally created by @southpolesteve on GitHub (Mar 6, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/295 ## Problem In `prod-server.ts` around line 944-945, middleware headers are merged into the final response using a `Record<string, string>` pattern: ```js const responseHeaders: Record<string, string> = { ...middlewareHeaders }; response.headers.forEach((v, k) => { responseHeaders[k] = v; }); ``` `Headers.forEach()` iterates Set-Cookie values as combined comma-separated strings (via `Headers.get()` semantics), or as individual entries depending on the runtime. Either way, the `Record<string, string>` target loses multiple Set-Cookie values: - If `forEach` yields per-entry, the last Set-Cookie wins (previous ones overwritten) - If `forEach` yields the comma-joined string, the result is a single corrupted Set-Cookie (cookie values with `Expires=` dates contain commas, so comma-joining breaks parsing) The resulting `sendCompressed` call passes this flattened object to `writeHead`, which can't reconstruct the original multiple Set-Cookie headers. ## Context This is a pre-existing bug, surfaced during review of #281 (which correctly uses `responseHeaders.append("set-cookie", ...)` in `renderPage`). Now that gSSP can set cookies via `res.setHeader("set-cookie", ...)`, this flattening in `prod-server.ts` becomes more likely to hit in practice. ## Fix Use `Headers.getSetCookie()` (available in Node 20+, Cloudflare Workers, Deno) to preserve array-valued Set-Cookie headers, or switch `responseHeaders` to use the `Headers` API throughout instead of a plain object. ## Discovered by Flagged by bigbonk in https://github.com/cloudflare/vinext/pull/281#issuecomment-4009074461
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#71
No description provided.