-
Notifications
You must be signed in to change notification settings - Fork 83
Description
It has been confirmed that this issues does not occur in the bun environment or the AWS Lambda environment, but only when using hono/node-server.
This issue can be reproduced when using the cors or compress middleware.
import { Hono } from 'hono';
import { compress } from 'hono/compress';
const app = new Hono();
app.use(compress());
app.post('/test', async c => {
const res = new Response('hello', { status: 200, headers: { 'Content-Type': 'application/json' } });
res.headers.append('Set-Cookie', 'session=abc; Path=/; HttpOnly');
return res;
});
export default app;It seems the raw response header was lost when the middleware modified the header.
Claude Code
When cloning a Response2 instance via new Response(body, existingResponse), headers that were appended after
construction (e.g. Set-Cookie via response.headers.append()) are silently dropped.
Root cause:
In the Response2 constructor, when init is another Response2 instance and no responseCache exists, headers are read
from init.#init.headers (the original ResponseInit passed at construction time) instead of init.headers (the current
headers via the getter, which includes all subsequent mutations):
// current (buggy)
if (init instanceof _Response) {
const cachedGlobalResponse = init[responseCache];
if (cachedGlobalResponse) {
this.#init = cachedGlobalResponse;
this[getResponseCache]();
return;
} else {
this.#init = init.#init;
headers = new Headers(init.#init.headers); // ← reads stale, original headers
}
}Response2 stores headers in two places:
- this[cacheKey][2] — the live Headers object returned by the headers getter, reflecting all mutations (.append(),
.set(), .delete()) - this.#init.headers — the original ResponseInit.headers passed to the constructor, never updated
The clone reads from #init.headers, so any header added after construction is lost.
Reproduction:
const res = new Response('hello', { status: 200, headers: { 'Content-Type': 'application/json' } });
res.headers.append('Set-Cookie', 'session=abc; Path=/; HttpOnly');
const cloned = new Response(res.body, res);
console.log(cloned.headers.get('set-cookie')); // null — should be "session=abc; Path=/; HttpOnly"This does not reproduce with the native Response (e.g. in Lambda / Workers runtimes), only with @hono/node-server's
Response2 override.
Impact:
Any Hono middleware that clones the response after finalization (e.g. cors() calling c.header() which triggers new
Response(this.#res.body, this.#res)) will lose Set-Cookie headers set by route handlers via raw Response objects.
This breaks cookie-based authentication in local Node.js development while working fine in production on
Workers/Lambda.
Fix:
} else {
this.#init = init.#init;
- headers = new Headers(init.#init.headers);
+ headers = new Headers(init.headers);
}init.headers goes through the getter, which returns the live Headers from cacheKey[2] containing all mutations. new
Headers(...) still creates a new independent copy, so parent and child do not share the same object.