From 14b3973abde53cefd7468ce88685a0944093ed90 Mon Sep 17 00:00:00 2001 From: Steven Chim <655241+chimurai@users.noreply.github.com> Date: Tue, 19 May 2026 20:18:09 +0000 Subject: [PATCH 1/6] chore(package.json): update to httpxy@0.5.2 --- CHANGELOG.md | 1 + package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba6ce8e2..c217d595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## next - feat(definePlugin): helper function to create plugins +- chore(package.json): update to httpxy@0.5.2 ## [v4.0.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v4.0.0) diff --git a/package.json b/package.json index 793d0484..3d30e3a4 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ }, "dependencies": { "debug": "^4.4.3", - "httpxy": "^0.5.1", + "httpxy": "^0.5.2", "is-glob": "^4.0.3", "is-plain-obj": "^4.1.0", "micromatch": "^4.0.8" diff --git a/yarn.lock b/yarn.lock index 4582c8ed..2af18860 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2532,10 +2532,10 @@ https-proxy-agent@^7.0.6: agent-base "^7.1.2" debug "4" -httpxy@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/httpxy/-/httpxy-0.5.1.tgz#c0cccd78672995968eee57666c3e3cfa822c2806" - integrity sha512-JPhqYiixe1A1I+MXDewWDZqeudBGU8Q9jCHYN8ML+779RQzLjTi78HBvWz4jMxUD6h2/vUL12g4q/mFM0OUw1A== +httpxy@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/httpxy/-/httpxy-0.5.2.tgz#b756cd1698b9838106de29775f96ff2b55d79777" + integrity sha512-C5OM92bmywDDdKTuYCGejdNFAb/zy0LX4srimOudYko847HvoWL2Eeik9odh9friPKu/JrlWv4z/amrJoxq2Cg== husky@9.1.7: version "9.1.7" From 83f80cfb80b792208446c17399bc43a0b65dd7d3 Mon Sep 17 00:00:00 2001 From: Steven Chim <655241+chimurai@users.noreply.github.com> Date: Tue, 19 May 2026 20:21:47 +0000 Subject: [PATCH 2/6] fix(ipv6): preserve credentials when normalizing bracketed IPv6 target string --- CHANGELOG.md | 1 + src/utils/ipv6.ts | 6 ++++++ test/e2e/ipv6.spec.ts | 4 ++-- test/unit/utils/ipv6.spec.ts | 17 +++++++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c217d595..00afc867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - feat(definePlugin): helper function to create plugins - chore(package.json): update to httpxy@0.5.2 +- fix(ipv6): preserve credentials when normalizing bracketed IPv6 target string ## [v4.0.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v4.0.0) diff --git a/src/utils/ipv6.ts b/src/utils/ipv6.ts index 4d492834..65e91d24 100644 --- a/src/utils/ipv6.ts +++ b/src/utils/ipv6.ts @@ -36,8 +36,14 @@ function normalizeIPv6ProxyTarget(target: Options['target'], optionName: 'target if (targetUrl && isBracketedIPv6Hostname(targetUrl.hostname)) { debug('normalized IPv6 "%s" %s', optionName, target); + const auth = + targetUrl.username || targetUrl.password + ? `${targetUrl.username}:${targetUrl.password}` + : undefined; + return { hostname: stripBrackets(targetUrl.hostname), + auth, pathname: targetUrl.pathname, port: targetUrl.port, protocol: targetUrl.protocol, diff --git a/test/e2e/ipv6.spec.ts b/test/e2e/ipv6.spec.ts index 73eb6516..d64dfec7 100644 --- a/test/e2e/ipv6.spec.ts +++ b/test/e2e/ipv6.spec.ts @@ -157,7 +157,7 @@ describe.runIf(await isIPv6Available())('ipv6 integration', () => { let receivedPath: string | undefined; let authorizationHeader: string | undefined; - await targetServer.forGet('/api').thenCallback((req) => { + await targetServer.forGet('/api/').thenCallback((req) => { receivedPath = req.path; const authHeader = req.headers.authorization; authorizationHeader = Array.isArray(authHeader) ? authHeader[0] : authHeader; @@ -176,7 +176,7 @@ describe.runIf(await isIPv6Available())('ipv6 integration', () => { const app = createApp(proxy); await request(app).get('/').expect(200); - expect(receivedPath).toBe('/api'); + expect(receivedPath).toBe('/api/'); expect(authorizationHeader).toBe('Basic dXNlcjpwYXNz'); // cspell:disable-line }); }); diff --git a/test/unit/utils/ipv6.spec.ts b/test/unit/utils/ipv6.spec.ts index f256eca3..49ffe664 100644 --- a/test/unit/utils/ipv6.spec.ts +++ b/test/unit/utils/ipv6.spec.ts @@ -47,6 +47,23 @@ describe('normalizeIPv6Targets()', () => { }); }); + it('should preserve credentials when normalizing bracketed IPv6 target string', () => { + const options: Options = { + target: 'http://user:pass@[::1]:8888/api', + }; + + normalizeIPv6LiteralTargets(options); + + expect(options.target).toEqual({ + auth: 'user:pass', + hostname: '::1', + pathname: '/api', + port: '8888', + protocol: 'http:', + search: '', + }); + }); + it('should normalize bracketed IPv6 target URL into a target object', () => { const options: Options = { target: new URL('http://[::1]:8888/api'), From 7c9a08aea328f6c516c9b4a884345fc56884b82d Mon Sep 17 00:00:00 2001 From: Steven Chim <655241+chimurai@users.noreply.github.com> Date: Wed, 20 May 2026 21:54:30 +0000 Subject: [PATCH 3/6] chore(package.json): bump to httpxy 0.5.3 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3d30e3a4..bd455e0f 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ }, "dependencies": { "debug": "^4.4.3", - "httpxy": "^0.5.2", + "httpxy": "^0.5.3", "is-glob": "^4.0.3", "is-plain-obj": "^4.1.0", "micromatch": "^4.0.8" diff --git a/yarn.lock b/yarn.lock index 2af18860..9fdf5f9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2532,10 +2532,10 @@ https-proxy-agent@^7.0.6: agent-base "^7.1.2" debug "4" -httpxy@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/httpxy/-/httpxy-0.5.2.tgz#b756cd1698b9838106de29775f96ff2b55d79777" - integrity sha512-C5OM92bmywDDdKTuYCGejdNFAb/zy0LX4srimOudYko847HvoWL2Eeik9odh9friPKu/JrlWv4z/amrJoxq2Cg== +httpxy@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/httpxy/-/httpxy-0.5.3.tgz#e9d4d9b584ec3a2c5d46d7d87635419e780353aa" + integrity sha512-SMS9V6Sn7VWaS11lYhoAr0ceoaiolTWf4jYdJn0NJhCdKMu9R2H9Fh0LBDWBHQF6HRLI1PmaePYsjanSpE5PEw== husky@9.1.7: version "9.1.7" From 843bb40d7c69544fe79e3c74c80b79380cf7358f Mon Sep 17 00:00:00 2001 From: Steven Chim <655241+chimurai@users.noreply.github.com> Date: Wed, 20 May 2026 22:07:42 +0000 Subject: [PATCH 4/6] revert: remove trailing slash --- test/e2e/ipv6.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/ipv6.spec.ts b/test/e2e/ipv6.spec.ts index d64dfec7..73eb6516 100644 --- a/test/e2e/ipv6.spec.ts +++ b/test/e2e/ipv6.spec.ts @@ -157,7 +157,7 @@ describe.runIf(await isIPv6Available())('ipv6 integration', () => { let receivedPath: string | undefined; let authorizationHeader: string | undefined; - await targetServer.forGet('/api/').thenCallback((req) => { + await targetServer.forGet('/api').thenCallback((req) => { receivedPath = req.path; const authHeader = req.headers.authorization; authorizationHeader = Array.isArray(authHeader) ? authHeader[0] : authHeader; @@ -176,7 +176,7 @@ describe.runIf(await isIPv6Available())('ipv6 integration', () => { const app = createApp(proxy); await request(app).get('/').expect(200); - expect(receivedPath).toBe('/api/'); + expect(receivedPath).toBe('/api'); expect(authorizationHeader).toBe('Basic dXNlcjpwYXNz'); // cspell:disable-line }); }); From 7fe82e1c1d439b9962747db713f82d3faef6d88c Mon Sep 17 00:00:00 2001 From: Steven Chim <655241+chimurai@users.noreply.github.com> Date: Wed, 20 May 2026 22:12:59 +0000 Subject: [PATCH 5/6] fix: unspecified IPv6 target hostname (::) --- src/utils/ipv6.ts | 14 +++++++++++++- test/unit/utils/ipv6.spec.ts | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/utils/ipv6.ts b/src/utils/ipv6.ts index 65e91d24..63f21c30 100644 --- a/src/utils/ipv6.ts +++ b/src/utils/ipv6.ts @@ -34,6 +34,8 @@ function normalizeIPv6ProxyTarget(target: Options['target'], optionName: 'target const targetUrl = toTargetUrl(target); if (targetUrl && isBracketedIPv6Hostname(targetUrl.hostname)) { + const normalizedHostname = normalizeIPv6DestinationHostname(stripBrackets(targetUrl.hostname)); + debug('normalized IPv6 "%s" %s', optionName, target); const auth = @@ -42,7 +44,7 @@ function normalizeIPv6ProxyTarget(target: Options['target'], optionName: 'target : undefined; return { - hostname: stripBrackets(targetUrl.hostname), + hostname: normalizedHostname, auth, pathname: targetUrl.pathname, port: targetUrl.port, @@ -73,3 +75,13 @@ function isBracketedIPv6Hostname(hostname: string): boolean { function stripBrackets(hostname: string): string { return hostname.replace(/^\[|\]$/g, ''); } + +function normalizeIPv6DestinationHostname(hostname: string): string { + // The unspecified address (::) is not a routable destination for outbound client requests. + // Treat it as loopback so a target like http://[::]:port reaches local IPv6 listeners. + if (hostname === '::') { + debug('normalizing hostname unspecified IPv6 address (::) to loopback (::1)'); + return '::1'; + } + return hostname; +} diff --git a/test/unit/utils/ipv6.spec.ts b/test/unit/utils/ipv6.spec.ts index 49ffe664..e85f1374 100644 --- a/test/unit/utils/ipv6.spec.ts +++ b/test/unit/utils/ipv6.spec.ts @@ -47,6 +47,22 @@ describe('normalizeIPv6Targets()', () => { }); }); + it('should normalize unspecified bracketed IPv6 destination to loopback', () => { + const options: Options = { + target: 'http://[::]:8888/api', + }; + + normalizeIPv6LiteralTargets(options); + + expect(options.target).toEqual({ + hostname: '::1', + pathname: '/api', + port: '8888', + protocol: 'http:', + search: '', + }); + }); + it('should preserve credentials when normalizing bracketed IPv6 target string', () => { const options: Options = { target: 'http://user:pass@[::1]:8888/api', From fc7d2f4960286d66c419a67f6ceb001487de554c Mon Sep 17 00:00:00 2001 From: Steven Chim <655241+chimurai@users.noreply.github.com> Date: Wed, 20 May 2026 22:14:45 +0000 Subject: [PATCH 6/6] docs: update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00afc867..c3c8a420 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ ## next - feat(definePlugin): helper function to create plugins -- chore(package.json): update to httpxy@0.5.2 +- chore(package.json): update to httpxy@0.5.3 - fix(ipv6): preserve credentials when normalizing bracketed IPv6 target string +- fix(ipv6): unspecified IPv6 target hostname (::)" ## [v4.0.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v4.0.0)