Four middleware packages strip the port from the Host header using raw.split(":", 1)[0], but String.prototype.split with a limit returns the first N pieces from a global split on every delimiter — it is not a Python-style split-once. For an IPv6 Host header like [::1]:443, "[::1]:443".split(":", 1) returns ["["] rather than "[::1]". The resulting "host" the guard compares against allowedHosts is the single character [, so every legitimate IPv6 client is rejected and every misconfigured guard that only knows about IPv4 happens to be "right" for the wrong reason.
The affected sites are packages/middleware/express/src/index.ts:82, packages/middleware/bun/src/index.ts:161, packages/middleware/hono/src/index.ts:78, and packages/middleware/node/src/index.ts:129. Three of these are byte-for-byte identical implementations of hostHeaderGuard and so share one fix; the bun one is an inline copy. There is no test fixture exercising an IPv6 Host header — a fix should add one.
Fix prompt: replace the raw.split(":", 1)[0] ?? "" lines with a bracket-aware parser. The simplest correct form is const lastColon = raw.lastIndexOf(":"); const lastBracket = raw.lastIndexOf("]"); const host = lastColon > lastBracket ? raw.slice(0, lastColon) : raw; which leaves [::1] intact, strips the :443 from [::1]:443, and leaves example.com and example.com:443 correct as well. Extract this helper into one place (e.g. packages/middleware/node/src/host.ts) and import from the other three middlewares so the implementations stay in sync. Add a table test for the helper covering "example.com", "example.com:80", "[::1]", "[::1]:443", "[2001:db8::1]:8080", "127.0.0.1:9000", and an empty string. Add an integration test against each middleware with an allowedHosts array including "[::1]" to assert IPv6 clients are not rejected.
Four middleware packages strip the port from the
Hostheader usingraw.split(":", 1)[0], butString.prototype.splitwith a limit returns the first N pieces from a global split on every delimiter — it is not a Python-style split-once. For an IPv6 Host header like[::1]:443,"[::1]:443".split(":", 1)returns["["]rather than"[::1]". The resulting "host" the guard compares againstallowedHostsis the single character[, so every legitimate IPv6 client is rejected and every misconfigured guard that only knows about IPv4 happens to be "right" for the wrong reason.The affected sites are
packages/middleware/express/src/index.ts:82,packages/middleware/bun/src/index.ts:161,packages/middleware/hono/src/index.ts:78, andpackages/middleware/node/src/index.ts:129. Three of these are byte-for-byte identical implementations ofhostHeaderGuardand so share one fix; the bun one is an inline copy. There is no test fixture exercising an IPv6 Host header — a fix should add one.Fix prompt: replace the
raw.split(":", 1)[0] ?? ""lines with a bracket-aware parser. The simplest correct form isconst lastColon = raw.lastIndexOf(":"); const lastBracket = raw.lastIndexOf("]"); const host = lastColon > lastBracket ? raw.slice(0, lastColon) : raw;which leaves[::1]intact, strips the:443from[::1]:443, and leavesexample.comandexample.com:443correct as well. Extract this helper into one place (e.g.packages/middleware/node/src/host.ts) and import from the other three middlewares so the implementations stay in sync. Add a table test for the helper covering"example.com","example.com:80","[::1]","[::1]:443","[2001:db8::1]:8080","127.0.0.1:9000", and an empty string. Add an integration test against each middleware with anallowedHostsarray including"[::1]"to assert IPv6 clients are not rejected.