From 235f8f274cc3725ad8db39ceed46c0bc1bc213dd Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 10 May 2026 19:24:43 -0500 Subject: [PATCH 1/2] feat: derive host from hostname for plain object targets and bracket IPv6 literals --- src/_utils.ts | 11 +++++++++++ test/_utils.test.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/_utils.ts b/src/_utils.ts index 5c4ea97..a06d131 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -80,6 +80,17 @@ export function setupOutgoing( } } + // Derive `host` from `hostname` (+ port) when the target was provided as a + // plain object without `host`. IPv6 literals must be bracketed in URI host + // syntax (RFC 3986 3.2.2), otherwise the `changeOrigin` branch below would + // produce a malformed `Host` header like `undefined:`. + if (outgoing.host === undefined && typeof outgoing.hostname === "string") { + const isIPv6Literal = + outgoing.hostname.includes(":") && !outgoing.hostname.startsWith("["); + const bracketedHost = isIPv6Literal ? `[${outgoing.hostname}]` : outgoing.hostname; + outgoing.host = outgoing.port ? `${bracketedHost}:${outgoing.port}` : bracketedHost; + } + outgoing.method = options.method || req.method; outgoing.headers = { ...req.headers }; diff --git a/test/_utils.test.ts b/test/_utils.test.ts index 9e1026f..0bd43ca 100644 --- a/test/_utils.test.ts +++ b/test/_utils.test.ts @@ -507,6 +507,40 @@ describe("lib/http-proxy/common.js", () => { ); expect(outgoing.headers!.host).to.eql("mycouch.com:6984"); }); + + it("should derive host from hostname when target is a plain object without host", () => { + const outgoing = createOutgoing(); + common.setupOutgoing( + outgoing, + { + target: { + protocol: "http:", + hostname: "example.com", + port: 8080, + }, + changeOrigin: true, + }, + stubIncomingMessage({ url: "/" }), + ); + expect(outgoing.headers!.host).to.eql("example.com:8080"); + }); + + it("should bracket IPv6 hostname when target is a plain object without host (RFC 3986 ยง3.2.2)", () => { + const outgoing = createOutgoing(); + common.setupOutgoing( + outgoing, + { + target: { + protocol: "http:", + hostname: "::1", + port: 8080, + }, + changeOrigin: true, + }, + stubIncomingMessage({ url: "/" }), + ); + expect(outgoing.headers!.host).to.eql("[::1]:8080"); + }); }); it("should pass through https client parameters", () => { From 06165accfc0c6615a8e30271557a47e869b26d86 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 09:36:48 +0000 Subject: [PATCH 2/2] chore: apply automated updates --- src/_utils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/_utils.ts b/src/_utils.ts index a06d131..02bada3 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -85,8 +85,7 @@ export function setupOutgoing( // syntax (RFC 3986 3.2.2), otherwise the `changeOrigin` branch below would // produce a malformed `Host` header like `undefined:`. if (outgoing.host === undefined && typeof outgoing.hostname === "string") { - const isIPv6Literal = - outgoing.hostname.includes(":") && !outgoing.hostname.startsWith("["); + const isIPv6Literal = outgoing.hostname.includes(":") && !outgoing.hostname.startsWith("["); const bracketedHost = isIPv6Literal ? `[${outgoing.hostname}]` : outgoing.hostname; outgoing.host = outgoing.port ? `${bracketedHost}:${outgoing.port}` : bracketedHost; }