@hapi/wreck leaks sensitive `Proxy-Authorization` header across cross-hostname redirects
Description
Impact
When @hapi/wreck follows a 3xx redirect to a different hostname, only the Authorization and Cookie headers are stripped. The standard credential header Proxy-Authorization is forwarded intact to the redirect target, potentially exposing forward-proxy credentials to a host outside the original trust boundary.
Redirect following is opt-in. The redirects option defaults to false (no redirections followed), so applications are only affected if they have explicitly set redirects to a positive integer on the request or via Wreck.defaults({ redirects: ... }).
Patches
@hapi/wreck 18.1.1 extends the cross-hostname strip set to include proxy-authorization. Upgrade to 18.1.1 or later.
Workarounds
If upgrading is not immediately possible: - Leave redirects at its default (false) — applications that never enable redirect following are not affected. - If redirects are required, set redirects: 0 when calling endpoints with sensitive headers, or strip Proxy-Authorization from the headers before issuing the request. - Use the beforeRedirect hook to manually strip proxy-authorization (and any other sensitive application headers) when redirectOptions targets a different hostname than the original request.
### Resources - Related: CVE-2024-30260 / GHSA-3787-6prv-h9w3 (undici) - RFC 7235 §4.4 — Proxy-Authorization
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In @hapi/wreck, the Proxy-Authorization header is not stripped on cross-hostname redirects, potentially exposing forward-proxy credentials.
Vulnerability
When @hapi/wreck follows a 3xx redirect to a different hostname, only the Authorization and Cookie headers are stripped [1]. The standard credential header Proxy-Authorization is forwarded intact to the redirect target, potentially exposing forward-proxy credentials to a host outside the original trust boundary [2]. This affects all versions of @hapi/wreck before 18.1.1 when the redirects option is set to a positive integer (default is false, so applications must explicitly enable redirect following) [3].
Exploitation
An attacker needs to control a server that sends a 3xx redirect to a different hostname in response to a request made by @hapi/wreck with the redirects option enabled. No authentication or user interaction is required beyond causing the redirect. The attacker can then observe the incoming request headers, including Proxy-Authorization, which is forwarded unchanged [2].
Impact
If an application uses forward proxy authentication via the Proxy-Authorization header and follows cross-hostname redirects, an attacker who controls the redirect target can obtain these credentials. This results in confidentiality loss of proxy credentials, potentially allowing the attacker to use the proxy or authenticate as the application to other services [2].
Mitigation
Upgrade to @hapi/wreck 18.1.1 or later, which adds proxy-authorization to the list of cross-host headers stripped on redirect [1]. If upgrading is not immediately possible, leave redirects at its default (false); if redirects are required, set redirects: 0 to disable following redirects, or manually strip Proxy-Authorization from headers before issuing the request, or use the beforeRedirect hook to strip it [2][3].
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1a5b6fac9c684fix: properly handle proxy-authorization
2 files changed · +38 −7
lib/index.js+4 −4 modified@@ -19,7 +19,8 @@ const Tap = require('./tap'); const internals = { jsonRegex: /^application\/([a-z0-9.]*[+-]json|json)$/, shallowOptions: ['agent', 'agents', 'beforeRedirect', 'payload', 'redirected'], - httpOptions: ['secureProtocol', 'ciphers', 'lookup', 'family', 'hints'] + httpOptions: ['secureProtocol', 'ciphers', 'lookup', 'family', 'hints'], + sensitiveCrossHostHeaders: new Set(['authorization', 'cookie', 'proxy-authorization']) }; @@ -248,13 +249,12 @@ internals.Client = class { redirectOptions.timeout = (redirectOptions.timeout - elapsed).toString(); // stringify to not drop timeout when === 0 } - // When redirecting to a new hostname, remove the authorization and cookie headers + // When redirecting to a new hostname, remove sensitive credential headers if (redirectOptions.headers) { const parsedLocation = new URL(location); if (uri.hostname !== parsedLocation.hostname) { for (const header of Object.keys(redirectOptions.headers)) { - const lowerHeader = header.toLowerCase(); - if (lowerHeader === 'authorization' || lowerHeader === 'cookie') { + if (internals.sensitiveCrossHostHeaders.has(header.toLowerCase())) { delete redirectOptions.headers[header]; } }
test/index.js+34 −3 modified@@ -408,7 +408,7 @@ describe('request()', () => { http2.close(); }); - it('handles redirections with new hostname, removing authorization and cookie headers', async (flags) => { + it('handles redirections with new hostname, removing authorization, cookie, and proxy-authorization headers', async (flags) => { const handler1 = (req, res) => { @@ -418,8 +418,8 @@ describe('request()', () => { const handler2 = (req, res) => { - // request must have 'x-foo' header, but must not have 'authorization' or 'cookie' - if (req.headers.authorization || req.headers.cookie || !req.headers['x-foo']) { + // request must have 'x-foo' header, but must not have 'authorization', 'cookie' or 'proxy-authorization' + if (req.headers.authorization || req.headers.cookie || req.headers['proxy-authorization'] || !req.headers['x-foo']) { res.writeHead(500); } @@ -432,6 +432,7 @@ describe('request()', () => { const headers = { authorization: 'some-auth-key', cookie: 'some-cookie', + 'proxy-authorization': 'some-proxy-auth', 'x-foo': 'something-else' }; @@ -441,6 +442,36 @@ describe('request()', () => { http2.close(); }); + it('preserves proxy-authorization header on same-hostname redirect', async (flags) => { + + let gen = 0; + const handler = (req, res) => { + + if (!gen++) { + res.writeHead(301, { 'Location': '/' }); + res.end(); + return; + } + + // proxy-authorization must persist across same-host redirect + if (req.headers['proxy-authorization'] !== 'some-proxy-auth') { + res.writeHead(500); + } + + res.end(); + }; + + const server = await internals.server(handler); + + const headers = { + 'proxy-authorization': 'some-proxy-auth' + }; + + const res = await Wreck.request('get', 'http://localhost:' + server.address().port, { redirects: 1, headers }); + expect(res.statusCode).to.equal(200); + server.close(); + }); + it('handles redirections from http to https', async (flags) => { const handler = (req, res) => {
Vulnerability mechanics
Root cause
"Missing `proxy-authorization` from the set of headers stripped on cross-hostname redirects allows credential leakage."
Attack vector
An attacker who controls a redirect target (e.g., via an open redirect on a trusted site or by serving a malicious 3xx response) can receive the client's `Proxy-Authorization` header when `@hapi/wreck` follows the redirect to a different hostname. The library only stripped `Authorization` and `Cookie` on cross-hostname redirects, but omitted `Proxy-Authorization` [patch_id=2595955]. The attacker can then use the leaked proxy credential to authenticate to the victim's forward proxy. Redirect following is opt-in (defaults to `false`), so only applications that explicitly set `redirects` to a positive integer are affected [ref_id=1].
Affected code
The vulnerability is in `lib/index.js` in the redirect-following logic of `@hapi/wreck`. When a 3xx redirect targets a different hostname, the code only stripped `authorization` and `cookie` headers via two inline string comparisons, leaving `proxy-authorization` forwarded intact. The patch introduces a `sensitiveCrossHostHeaders` Set containing all three header names and uses it in the cross-hostname header-filtering loop.
What the fix does
The patch replaces the two inline string comparisons (`lowerHeader === 'authorization' || lowerHeader === 'cookie'`) with a single lookup against a new `internals.sensitiveCrossHostHeaders` Set that includes `'proxy-authorization'` alongside the two existing headers [patch_id=2595955]. This ensures that when a redirect goes to a different hostname, all three credential headers are stripped from the forwarded request. The same-hostname redirect test confirms that `proxy-authorization` is preserved when the hostname does not change, matching the intended behavior.
Preconditions
- configApplication must explicitly set redirects to a positive integer (e.g., redirects: 1) on the request or via Wreck.defaults({ redirects: ... })
- networkThe attacker must be able to serve a 3xx redirect response pointing to a different hostname than the original request
- inputThe original request must include a Proxy-Authorization header
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.