Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function previewServerPlugin(): Plugin {
// Temporary workaround
// Vite preview's compression middleware doesn't support flattened array headers that srvx sets
// Call writeHead() before srvx to avoid corruption
res.setHeaders(webRes.headers)
webRes.headers.forEach((value, key) => res.setHeader(key, value));
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

For Node.js Fetch Headers, does headers.forEachpreserve multipleSet-Cookievalues distinctly, and what is the recommended way to forward them tohttp.ServerResponse/Http2ServerResponse without losing cookies?

💡 Result:

No, Node.js Fetch Headers.forEach does not preserve multiple Set-Cookie values distinctly. The Fetch Headers object follows the WHATWG spec where multiple values for the same header (including Set-Cookie) are combined into a single comma-separated string. Thus, headers.forEach((value, name) => ...) will iterate once over 'set-cookie' with value as the concatenated string (e.g., "cookie1=val1, cookie2=val2"), not once per cookie. This makes parsing unreliable due to commas in cookie attributes like Expires. The recommended way to access distinct Set-Cookie values and forward them to http.ServerResponse or Http2ServerResponse without loss: 1. Use response.headers.getSetCookie: Returns an array of individual Set-Cookie strings (Node.js 18.10.0+, undici-based Fetch). const cookies = response.headers.getSetCookie; 2. Set on ServerResponse: res.setHeader('Set-Cookie', cookies); // Array accepted, sends multiple distinct headers. Example proxy code: const { fetch } = require('undici'); // or global fetch in Node 18+ async function proxy(req, res) { const upstreamRes = await fetch(upstreamUrl); // Copy other headers... for (const [name, value] of upstreamRes.headers) { if (name.toLowerCase !== 'set-cookie') { res.setHeader(name, value); } } // Handle Set-Cookie distinctly const setCookies = upstreamRes.headers.getSetCookie; if (setCookies.length > 0) { res.setHeader('Set-Cookie', setCookies); } res.statusCode = upstreamRes.status; // Pipe body... } This ensures multiple Set-Cookie headers are forwarded as separate lines, per RFC 6265 recommendation against comma-folding Set-Cookie. Fallback for older node-fetch (non-standard): response.headers.raw['set-cookie'] returns array of strings. Node.js http.ServerResponse.setHeader('Set-Cookie', array) correctly emits multiple headers.

Citations:


🏁 Script executed:

# Check the context around line 57 and determine the type of webRes
sed -n '40,75p' packages/start-plugin-core/src/preview-server-plugin/plugin.ts

Repository: TanStack/router

Length of output: 1123


🏁 Script executed:

# Check Node.js version requirement in package.json or similar
cat packages/start-plugin-core/package.json | grep -A 5 -B 5 "engines\|node"

Repository: TanStack/router

Length of output: 1084


🏁 Script executed:

# Search for getSetCookie usage elsewhere in the codebase
rg "getSetCookie" --type ts --type tsx -A 2 -B 2

Repository: TanStack/router

Length of output: 86


🏁 Script executed:

# Check if Response or webRes is typed somewhere
ast-grep --pattern 'let $_ = $$$' packages/start-plugin-core/src/preview-server-plugin/plugin.ts

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Check what sendNodeResponse does with headers/body
rg "sendNodeResponse" --type ts -A 10 -B 2 | head -50

Repository: TanStack/router

Length of output: 4516


🏁 Script executed:

# Look for the sendNodeResponse implementation
fd "sendNodeResponse\|send-node-response" --type ts --type tsx

Repository: TanStack/router

Length of output: 228


🏁 Script executed:

# Check if there's a common pattern for forwarding headers in the codebase
rg "headers\.forEach\|getSetCookie" --type ts -C 3

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Get full view of the function containing line 57 to understand complete flow
sed -n '1,100p' packages/start-plugin-core/src/preview-server-plugin/plugin.ts | tail -60

Repository: TanStack/router

Length of output: 2551


🏁 Script executed:

# Check srvx dependency version and see if there's any documentation
cat packages/start-plugin-core/package.json | grep -i srvx

Repository: TanStack/router

Length of output: 81


🏁 Script executed:

# Search for any other patterns of header forwarding or Set-Cookie handling in the codebase
rg "Set-Cookie|set-cookie" --type-add 'ts:*.{ts,tsx}' --type ts -i -C 2

Repository: TanStack/router

Length of output: 8518


🌐 Web query:

What is the srvx library for Node.js and how does sendNodeResponse handle headers from Fetch Response objects, particularly multi-value headers like Set-Cookie?

💡 Result:

srvx is a universal server library for Node.js, Deno, and Bun that implements web standard APIs (Request/Response/fetch handlers) with a full-featured CLI for development (watcher, logger, static serving). It provides seamless runtime integration and Node.js compatibility with near-native performance using lightweight proxies to wrap node:IncomingMessage as Request and convert Response to node:ServerResponse. sendNodeResponse is an internal utility in srvx's Node.js adapter (src/adapters/_node/send.ts) that converts a web-standard Fetch Response object to a Node.js ServerResponse. It sets the status, statusText, and headers on the Node.js response using res.writeHead. For headers, it processes the Response.headers using .flat multiple times to handle multi-value headers correctly, passing them as a raw array of [name, value] pairs (which Node.js accepts natively for efficiency). Specifically for multi-value headers like Set-Cookie, it properly splits and preserves all values (using cookie-es internally), ensuring all cookies are sent as separate header lines in the HTTP response. Past issues with header formatting (e.g., invalid output) were resolved and unrelated to srvx core (due to middleware like morgan), confirming robust handling in current versions (v0.11+ as of 2026).

Citations:


Use getSetCookie() to preserve multiple Set-Cookie headers.

Line 57 iterates headers with forEach(), which combines multiple Set-Cookie values into a single comma-separated string. This loses distinct cookies before writeHead() locks them in. Call getSetCookie() to retrieve them as an array and pass the array to res.setHeader('Set-Cookie', ...), which Node.js correctly emits as multiple headers. This matters for session/auth cookies and aligns with existing multi-cookie preservation logic in the codebase.

Proposed fix
- webRes.headers.forEach((value, key) => res.setHeader(key, value));
+ webRes.headers.forEach((value, key) => {
+   if (key.toLowerCase() !== 'set-cookie') {
+     res.setHeader(key, value)
+   }
+ })
+
+ const setCookies = webRes.headers.getSetCookie()
+ if (setCookies.length > 0) {
+   res.setHeader('set-cookie', setCookies)
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
webRes.headers.forEach((value, key) => res.setHeader(key, value));
webRes.headers.forEach((value, key) => {
if (key.toLowerCase() !== 'set-cookie') {
res.setHeader(key, value)
}
})
const setCookies = webRes.headers.getSetCookie()
if (setCookies.length > 0) {
res.setHeader('set-cookie', setCookies)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/src/preview-server-plugin/plugin.ts` at line 57,
The current header-copying loop using webRes.headers.forEach(...) can merge
multiple Set-Cookie values into one string; instead, use webRes.getSetCookie()
to retrieve the Set-Cookie array and call res.setHeader('Set-Cookie',
cookieArray) so Node emits multiple Set-Cookie headers; keep the existing loop
for other headers but ensure you set the Set-Cookie header from
webRes.getSetCookie() before writeHead()/end() (refer to webRes.getSetCookie(),
res.setHeader('Set-Cookie', ...) and the existing webRes.headers.forEach usage).

res.writeHead(webRes.status, webRes.statusText)

return sendNodeResponse(res, webRes)
Expand Down