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 #281 (comment)
Problem
In
prod-server.tsaround line 944-945, middleware headers are merged into the final response using aRecord<string, string>pattern:Headers.forEach()iterates Set-Cookie values as combined comma-separated strings (viaHeaders.get()semantics), or as individual entries depending on the runtime. Either way, theRecord<string, string>target loses multiple Set-Cookie values:forEachyields per-entry, the last Set-Cookie wins (previous ones overwritten)forEachyields the comma-joined string, the result is a single corrupted Set-Cookie (cookie values withExpires=dates contain commas, so comma-joining breaks parsing)The resulting
sendCompressedcall passes this flattened object towriteHead, 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", ...)inrenderPage). Now that gSSP can set cookies viares.setHeader("set-cookie", ...), this flattening inprod-server.tsbecomes 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 switchresponseHeadersto use theHeadersAPI throughout instead of a plain object.Discovered by
Flagged by bigbonk in #281 (comment)