VYPR
Medium severity6.5GHSA Advisory· Published Jun 11, 2026· Updated Jun 11, 2026

@hapi/wreck: Sensitive credential headers leak across cross-port and cross-scheme redirects

CVE-2026-48022

Description

Wreck HTTP client leaks credential headers across cross-origin redirects that differ only in port or scheme, allowing credential theft; fixed in 18.1.2.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Wreck HTTP client leaks credential headers across cross-origin redirects that differ only in port or scheme, allowing credential theft; fixed in 18.1.2.

Vulnerability

In Wreck (HTTP client library for hapi.js), versions prior to 18.1.2, the cross-origin redirect check only compares hostnames, ignoring scheme and port. This causes credential headers (Authorization, Cookie, Proxy-Authorization) to be forwarded when redirecting to a different port on the same host or from HTTPS to HTTP, violating the same-origin policy.

Exploitation

An attacker who can control a redirect response (e.g., via a co-tenant on an adjacent port, or a network position that can forge HTTP redirects) can cause the client to send sensitive credentials to a different origin. No user interaction beyond following the redirect is required; the client automatically follows redirects if redirects is set to a positive number (default is 0, but users may enable it).

Impact

Successful exploitation allows the attacker to capture bearer tokens, session cookies, and proxy credentials, enabling impersonation of the victim against the upstream service. This leads to unauthorized access and potential data breach.

Mitigation

Upgrade to Wreck version 18.1.2 or later, which performs a full origin comparison (scheme, host, port). Workarounds include setting redirects: 0 (the default) and handling redirects manually with a strict origin check, or using the beforeRedirect hook to inspect the redirect target and strip sensitive headers before the follow-on request. [1][2]

AI Insight generated on Jun 11, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

1
b93323b63ad3

fix: cross-origin redirect should also remove sensitive headers

https://github.com/hapijs/wreckNicolas MorelMay 20, 2026via ghsa-ref
2 files changed · +90 4
  • lib/index.js+2 2 modified
    @@ -249,10 +249,10 @@ internals.Client = class {
                     redirectOptions.timeout = (redirectOptions.timeout - elapsed).toString();           // stringify to not drop timeout when === 0
                 }
     
    -            // When redirecting to a new hostname, remove sensitive credential headers
    +            // When redirecting cross-origin (scheme, host, or port differs), remove sensitive credential headers
                 if (redirectOptions.headers) {
                     const parsedLocation = new URL(location);
    -                if (uri.hostname !== parsedLocation.hostname) {
    +                if (uri.origin !== parsedLocation.origin) {
                         for (const header of Object.keys(redirectOptions.headers)) {
                             if (internals.sensitiveCrossHostHeaders.has(header.toLowerCase())) {
                                 delete redirectOptions.headers[header];
    
  • test/index.js+88 2 modified
    @@ -442,6 +442,75 @@ describe('request()', () => {
             http2.close();
         });
     
    +    it('removes sensitive headers when redirected to a different port on the same hostname', async (flags) => {
    +
    +        const handler2 = (req, res) => {
    +
    +            if (req.headers.authorization || req.headers.cookie || req.headers['proxy-authorization']) {
    +                res.writeHead(500);
    +                return res.end();
    +            }
    +
    +            res.writeHead(200);
    +            res.end();
    +        };
    +
    +        const http2 = await internals.server(handler2);
    +
    +        const handler1 = (req, res) => {
    +
    +            res.writeHead(302, { 'Location': 'http://localhost:' + http2.address().port });
    +            res.end();
    +        };
    +
    +        const http1 = await internals.server(handler1);
    +
    +        const headers = {
    +            authorization: 'some-auth-key',
    +            cookie: 'some-cookie',
    +            'proxy-authorization': 'some-proxy-auth'
    +        };
    +
    +        const res = await Wreck.request('get', 'http://localhost:' + http1.address().port, { redirects: 1, headers });
    +        expect(res.statusCode).to.equal(200);
    +        http1.close();
    +        http2.close();
    +    });
    +
    +    it('removes sensitive headers when redirected to a different scheme on the same hostname', async (flags) => {
    +
    +        const handler1 = (req, res) => {
    +
    +            res.writeHead(302, { 'Location': 'https://127.0.0.1:' + https.address().port });
    +            res.end();
    +        };
    +
    +        const handler2 = (req, res) => {
    +
    +            if (req.headers.authorization || req.headers.cookie || req.headers['proxy-authorization']) {
    +                res.writeHead(500);
    +                return res.end();
    +            }
    +
    +            res.writeHead(200);
    +            res.end();
    +        };
    +
    +        const https = await internals.https(handler2);
    +        const http = await internals.server(handler1);
    +
    +        const headers = {
    +            authorization: 'some-auth-key',
    +            cookie: 'some-cookie',
    +            'proxy-authorization': 'some-proxy-auth'
    +        };
    +
    +        const res = await Wreck.request('get', 'http://localhost:' + http.address().port, { redirects: 1, rejectUnauthorized: false, headers });
    +        expect(res.statusCode).to.equal(200);
    +        http.close();
    +        https.close();
    +    });
    +
         it('preserves proxy-authorization header on same-hostname redirect', async (flags) => {
     
             let gen = 0;
    @@ -1303,9 +1372,10 @@ describe('options.baseUrl', () => {
     
         it('uses path when path is a full URL', async (flags) => {
     
    -        const promise = Wreck.request('get', 'http://localhost:8080/foo', { baseUrl: 'http://localhost:0/' });
    +        const unboundPort = await internals.unusedPort();
    +        const promise = Wreck.request('get', `http://localhost:${unboundPort}/foo`, { baseUrl: 'http://localhost:0/' });
             await expect(promise).to.reject();
    -        expect(promise.req.getHeader('host')).to.equal('localhost:8080');
    +        expect(promise.req.getHeader('host')).to.equal(`localhost:${unboundPort}`);
         });
     
         it('uses lower-case host header when path is not a full URL', async (flags) => {
    @@ -2601,6 +2671,22 @@ describe('Defaults', () => {
     });
     
     
    +internals.unusedPort = function () {
    +
    +    return new Promise((resolve, reject) => {
    +
    +        const server = Http.createServer();
    +        server.unref();
    +        server.on('error', reject);
    +        server.listen(0, '127.0.0.1', () => {
    +
    +            const { port } = server.address();
    +            server.close(() => resolve(port));
    +        });
    +    });
    +};
    +
    +
     internals.server = function (handler, socket) {
     
         if (typeof handler !== 'function') {
    

Vulnerability mechanics

Root cause

"The origin check in the redirect handler compared only hostnames, ignoring scheme and port, so credentials were forwarded to cross-origin targets that shared the same hostname."

Attack vector

An attacker who can control a redirect response (e.g., through a malicious upstream, a man-in-the-middle position, or a co-tenant on an adjacent port) can craft a `Location` header that differs only in scheme or port while keeping the same hostname. Because the old code compared only hostnames, Wreck would forward sensitive headers (`Authorization`, `Cookie`, `Proxy-Authorization`) to the cross-origin target. This allows credential theft and session hijacking.

Affected code

The vulnerability is in `lib/index.js` in the redirect-handling logic of the `internals.Client` class. The origin check at line 252 used `uri.hostname !== parsedLocation.hostname`, which compared only hostnames and ignored scheme and port. The patch changes this to `uri.origin !== parsedLocation.origin` to perform a full origin comparison.

What the fix does

The patch replaces the hostname-only comparison (`uri.hostname !== parsedLocation.hostname`) with a full-origin comparison (`uri.origin !== parsedLocation.origin`) in `lib/index.js`. This ensures that sensitive credential headers are stripped whenever the scheme, host, or port differs between the original request and the redirect target, aligning with the WHATWG Fetch same-origin definition.

Preconditions

  • networkThe attacker must be able to influence the redirect target (e.g., via a malicious upstream service, a network position that can inject redirect headers, or a co-tenant on a different port of the same host).
  • configThe Wreck client must be configured with `redirects > 0` (default is 0, but any positive value enables automatic redirect following).
  • inputThe initial request must include at least one of the sensitive headers: `Authorization`, `Cookie`, or `Proxy-Authorization`.

Generated on Jun 11, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.