VYPR
High severityNVD Advisory· Published Jan 10, 2022· Updated Aug 2, 2024

Exposure of Private Personal Information to an Unauthorized Actor in follow-redirects/follow-redirects

CVE-2022-0155

Description

follow-redirects is vulnerable to Exposure of Private Personal Information to an Unauthorized Actor

AI Insight

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

follow-redirects leaks cookies across cross-domain redirects, potentially exposing sensitive data to unauthorized hosts.

## Vulnerability follow-redirects versions prior to the commit 8b347cb (fixed in unspecified release) improperly retain cookies when following HTTP redirects to a different domain. The module is a drop-in replacement for Node.js http and https that automatically follows redirects. Under normal operation, the Authorization header was already stripped on cross-domain redirects, but the Cookie header was not. This oversight can expose private personal information to an unauthorized actor [1][3].

Exploitation

An attacker does not need special network position; they can host a malicious server that issues an HTTP redirect (e.g., 301, 302, 307) to a different domain under their control. When a client using an affected version of follow-redirects makes a request that includes cookies (e.g., via a Cookie header set by the application), the module will forward those cookies to the attacker's domain. No authentication or user interaction beyond making the initial request is required [3][4].

Impact

Successful exploitation allows an attacker to obtain cookies intended for the original domain. These cookies may contain session tokens or other sensitive personal information, leading to unauthorized access, session hijacking, or exposure of private data. The impact is limited to cookie disclosure; depending on the application’s use of cookies, the attacker may impersonate the victim [2][4].

Mitigation

The fix is implemented in commit 8b347cb on the follow-redirects repository, where the removeMatchingHeaders call now also matches the cookie header (in addition to authorization). Users should update to the patched version (if a release is published) or apply the commit directly. No workaround is available other than manually stripping the Cookie header on redirects. Siemens also references this CVE in their advisory ssa-637483 [2][3][4].

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

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
follow-redirectsnpm
< 1.14.71.14.7

Affected products

93

Patches

1
8b347cbcef7c

Drop Cookie header across domains.

2 files changed · +175 182
  • index.js+2 2 modified
    @@ -392,9 +392,9 @@ RedirectableRequest.prototype._processResponse = function (response) {
         var redirectUrlParts = url.parse(redirectUrl);
         Object.assign(this._options, redirectUrlParts);
     
    -    // Drop the Authorization header if redirecting to another domain
    +    // Drop the confidential headers when redirecting to another domain
         if (!(redirectUrlParts.host === currentHost || isSubdomainOf(redirectUrlParts.host, currentHost))) {
    -      removeMatchingHeaders(/^authorization$/i, this._options.headers);
    +      removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
         }
     
         // Evaluate the beforeRedirect callback
    
  • test/test.js+173 180 modified
    @@ -1319,215 +1319,208 @@ describe("follow-redirects", function () {
         });
       });
     
    -  describe("when the client passes an Authorization header", function () {
    -    it("ignores it when null", function () {
    -      app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
    -      app.get("/b", function (req, res) {
    -        res.end(JSON.stringify(req.headers));
    -      });
    -
    -      var opts = url.parse("http://127.0.0.1:3600/a");
    -      opts.headers = {
    -        host: "localhost",
    -        authorization: null,
    -      };
    -
    -      return server.start(app)
    -        .then(asPromise(function (resolve, reject) {
    -          http.get(opts, resolve).on("error", reject);
    -        }))
    -        .then(asPromise(function (resolve, reject, res) {
    -          res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    -        }))
    -        .then(function (str) {
    -          var body = JSON.parse(str);
    -          assert.equal(body.host, "localhost:3600");
    -          assert.equal(body.authorization, undefined);
    +  [
    +    "Authorization",
    +    "Cookie",
    +  ].forEach(function (header) {
    +    describe("when the client passes an header named " + header, function () {
    +      it("ignores it when null", function () {
    +        app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
    +        app.get("/b", function (req, res) {
    +          res.end(JSON.stringify(req.headers));
             });
    -    });
     
    -    it("keeps the header when redirected to the same host", function () {
    -      app.get("/a", redirectsTo(302, "/b"));
    -      app.get("/b", function (req, res) {
    -        res.end(JSON.stringify(req.headers));
    -      });
    +        var opts = url.parse("http://127.0.0.1:3600/a");
    +        opts.headers = { host: "localhost" };
    +        opts.headers[header] = null;
     
    -      var opts = url.parse("http://localhost:3600/a");
    -      opts.headers = {
    -        authorization: "bearer my-token-1234",
    -      };
    +        return server.start(app)
    +          .then(asPromise(function (resolve, reject) {
    +            http.get(opts, resolve).on("error", reject);
    +          }))
    +          .then(asPromise(function (resolve, reject, res) {
    +            res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    +          }))
    +          .then(function (str) {
    +            var body = JSON.parse(str);
    +            assert.equal(body.host, "localhost:3600");
    +            assert.equal(body[header.toLowerCase()], undefined);
    +          });
    +      });
     
    -      return server.start(app)
    -        .then(asPromise(function (resolve, reject) {
    -          http.get(opts, resolve).on("error", reject);
    -        }))
    -        .then(asPromise(function (resolve, reject, res) {
    -          res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    -        }))
    -        .then(function (str) {
    -          var body = JSON.parse(str);
    -          assert.equal(body.host, "localhost:3600");
    -          assert.equal(body.authorization, "bearer my-token-1234");
    +      it("keeps the header when redirected to the same host", function () {
    +        app.get("/a", redirectsTo(302, "/b"));
    +        app.get("/b", function (req, res) {
    +          res.end(JSON.stringify(req.headers));
             });
    -    });
     
    -    it("keeps the header when redirected to the same host via header", function () {
    -      app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
    -      app.get("/b", function (req, res) {
    -        res.end(JSON.stringify(req.headers));
    -      });
    +        var opts = url.parse("http://localhost:3600/a");
    +        opts.headers = {};
    +        opts.headers[header] = "the header value";
     
    -      var opts = url.parse("http://127.0.0.1:3600/a");
    -      opts.headers = {
    -        host: "localhost:3600",
    -        authorization: "bearer my-token-1234",
    -      };
    +        return server.start(app)
    +          .then(asPromise(function (resolve, reject) {
    +            http.get(opts, resolve).on("error", reject);
    +          }))
    +          .then(asPromise(function (resolve, reject, res) {
    +            res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    +          }))
    +          .then(function (str) {
    +            var body = JSON.parse(str);
    +            assert.equal(body.host, "localhost:3600");
    +            assert.equal(body[header.toLowerCase()], "the header value");
    +          });
    +      });
     
    -      return server.start(app)
    -        .then(asPromise(function (resolve, reject) {
    -          http.get(opts, resolve).on("error", reject);
    -        }))
    -        .then(asPromise(function (resolve, reject, res) {
    -          res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    -        }))
    -        .then(function (str) {
    -          var body = JSON.parse(str);
    -          assert.equal(body.host, "localhost:3600");
    -          assert.equal(body.authorization, "bearer my-token-1234");
    +      it("keeps the header when redirected to the same host via header", function () {
    +        app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
    +        app.get("/b", function (req, res) {
    +          res.end(JSON.stringify(req.headers));
             });
    -    });
     
    -    it("keeps the header when redirected to the same host via header", function () {
    -      app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
    -      app.get("/b", function (req, res) {
    -        res.end(JSON.stringify(req.headers));
    -      });
    +        var opts = url.parse("http://127.0.0.1:3600/a");
    +        opts.headers = { host: "localhost:3600" };
    +        opts.headers[header] = "the header value";
     
    -      var opts = url.parse("http://127.0.0.1:3600/a");
    -      opts.headers = {
    -        host: "localhost:3600",
    -        authorization: "bearer my-token-1234",
    -      };
    +        return server.start(app)
    +          .then(asPromise(function (resolve, reject) {
    +            http.get(opts, resolve).on("error", reject);
    +          }))
    +          .then(asPromise(function (resolve, reject, res) {
    +            res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    +          }))
    +          .then(function (str) {
    +            var body = JSON.parse(str);
    +            assert.equal(body.host, "localhost:3600");
    +            assert.equal(body[header.toLowerCase()], "the header value");
    +          });
    +      });
     
    -      return server.start(app)
    -        .then(asPromise(function (resolve, reject) {
    -          http.get(opts, resolve).on("error", reject);
    -        }))
    -        .then(asPromise(function (resolve, reject, res) {
    -          res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    -        }))
    -        .then(function (str) {
    -          var body = JSON.parse(str);
    -          assert.equal(body.host, "localhost:3600");
    -          assert.equal(body.authorization, "bearer my-token-1234");
    +      it("keeps the header when redirected to the same host via header", function () {
    +        app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
    +        app.get("/b", function (req, res) {
    +          res.end(JSON.stringify(req.headers));
             });
    -    });
     
    -    it("keeps the header when redirected to a subdomain", function () {
    -      app.get("/a", redirectsTo(302, "http://sub.localhost:3600/b"));
    -      app.get("/b", function (req, res) {
    -        res.end(JSON.stringify(req.headers));
    -      });
    +        var opts = url.parse("http://127.0.0.1:3600/a");
    +        opts.headers = { host: "localhost:3600" };
    +        opts.headers[header] = "the header value";
     
    -      var opts = url.parse("http://localhost:3600/a");
    -      opts.headers = {
    -        authorization: "bearer my-token-1234",
    -      };
    -      // Intercept the hostname, as no DNS entry is defined for it
    -      opts.beforeRedirect = function (options) {
    -        assert.equal(options.hostname, "sub.localhost");
    -        options.hostname = "localhost";
    -      };
    +        return server.start(app)
    +          .then(asPromise(function (resolve, reject) {
    +            http.get(opts, resolve).on("error", reject);
    +          }))
    +          .then(asPromise(function (resolve, reject, res) {
    +            res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    +          }))
    +          .then(function (str) {
    +            var body = JSON.parse(str);
    +            assert.equal(body.host, "localhost:3600");
    +            assert.equal(body[header.toLowerCase()], "the header value");
    +          });
    +      });
     
    -      return server.start(app)
    -        .then(asPromise(function (resolve, reject) {
    -          http.get(opts, resolve).on("error", reject);
    -        }))
    -        .then(asPromise(function (resolve, reject, res) {
    -          res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    -        }))
    -        .then(function (str) {
    -          var body = JSON.parse(str);
    -          assert.equal(body.host, "localhost:3600");
    -          assert.equal(body.authorization, "bearer my-token-1234");
    +      it("keeps the header when redirected to a subdomain", function () {
    +        app.get("/a", redirectsTo(302, "http://sub.localhost:3600/b"));
    +        app.get("/b", function (req, res) {
    +          res.end(JSON.stringify(req.headers));
             });
    -    });
     
    -    it("drops the header when redirected to a different host (same hostname and different port)", function () {
    -      app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
    -      app.get("/b", function (req, res) {
    -        res.end(JSON.stringify(req.headers));
    -      });
    +        var opts = url.parse("http://localhost:3600/a");
    +        opts.headers = {};
    +        opts.headers[header] = "the header value";
     
    -      var opts = url.parse("http://127.0.0.1:3600/a");
    -      opts.headers = {
    -        host: "localhost",
    -        authorization: "bearer my-token-1234",
    -      };
    +        // Intercept the hostname, as no DNS entry is defined for it
    +        opts.beforeRedirect = function (options) {
    +          assert.equal(options.hostname, "sub.localhost");
    +          options.hostname = "localhost";
    +        };
     
    -      return server.start(app)
    -        .then(asPromise(function (resolve, reject) {
    -          http.get(opts, resolve).on("error", reject);
    -        }))
    -        .then(asPromise(function (resolve, reject, res) {
    -          res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    -        }))
    -        .then(function (str) {
    -          var body = JSON.parse(str);
    -          assert.equal(body.host, "localhost:3600");
    -          assert.equal(body.authorization, undefined);
    +        return server.start(app)
    +          .then(asPromise(function (resolve, reject) {
    +            http.get(opts, resolve).on("error", reject);
    +          }))
    +          .then(asPromise(function (resolve, reject, res) {
    +            res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    +          }))
    +          .then(function (str) {
    +            var body = JSON.parse(str);
    +            assert.equal(body.host, "localhost:3600");
    +            assert.equal(body[header.toLowerCase()], "the header value");
    +          });
    +      });
    +
    +      it("drops the header when redirected to a different host (same hostname and different port)", function () {
    +        app.get("/a", redirectsTo(302, "http://localhost:3600/b"));
    +        app.get("/b", function (req, res) {
    +          res.end(JSON.stringify(req.headers));
             });
    -    });
     
    -    it("drops the header when redirected to a different host", function () {
    -      app.get("/a", redirectsTo(302, "http://127.0.0.1:3600/b"));
    -      app.get("/b", function (req, res) {
    -        res.end(JSON.stringify(req.headers));
    -      });
    +        var opts = url.parse("http://127.0.0.1:3600/a");
    +        opts.headers = { host: "localhost" };
    +        opts.headers[header] = "the header value";
     
    -      var opts = url.parse("http://localhost:3600/a");
    -      opts.headers = {
    -        authorization: "bearer my-token-1234",
    -      };
    +        return server.start(app)
    +          .then(asPromise(function (resolve, reject) {
    +            http.get(opts, resolve).on("error", reject);
    +          }))
    +          .then(asPromise(function (resolve, reject, res) {
    +            res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    +          }))
    +          .then(function (str) {
    +            var body = JSON.parse(str);
    +            assert.equal(body.host, "localhost:3600");
    +            assert.equal(body[header.toLowerCase()], undefined);
    +          });
    +      });
     
    -      return server.start(app)
    -        .then(asPromise(function (resolve, reject) {
    -          http.get(opts, resolve).on("error", reject);
    -        }))
    -        .then(asPromise(function (resolve, reject, res) {
    -          res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    -        }))
    -        .then(function (str) {
    -          var body = JSON.parse(str);
    -          assert.equal(body.host, "127.0.0.1:3600");
    -          assert.equal(body.authorization, undefined);
    +      it("drops the header when redirected to a different host", function () {
    +        app.get("/a", redirectsTo(302, "http://127.0.0.1:3600/b"));
    +        app.get("/b", function (req, res) {
    +          res.end(JSON.stringify(req.headers));
             });
    -    });
     
    -    it("drops the header when redirected from a different host via header", function () {
    -      app.get("/a", redirectsTo(302, "http://127.0.0.1:3600/b"));
    -      app.get("/b", function (req, res) {
    -        res.end(JSON.stringify(req.headers));
    -      });
    +        var opts = url.parse("http://localhost:3600/a");
    +        opts.headers = {};
    +        opts.headers[header] = "the header value";
     
    -      var opts = url.parse("http://127.0.0.1:3600/a");
    -      opts.headers = {
    -        host: "localhost",
    -        authorization: "bearer my-token-1234",
    -      };
    +        return server.start(app)
    +          .then(asPromise(function (resolve, reject) {
    +            http.get(opts, resolve).on("error", reject);
    +          }))
    +          .then(asPromise(function (resolve, reject, res) {
    +            res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    +          }))
    +          .then(function (str) {
    +            var body = JSON.parse(str);
    +            assert.equal(body.host, "127.0.0.1:3600");
    +            assert.equal(body[header.toLowerCase()], undefined);
    +          });
    +      });
     
    -      return server.start(app)
    -        .then(asPromise(function (resolve, reject) {
    -          http.get(opts, resolve).on("error", reject);
    -        }))
    -        .then(asPromise(function (resolve, reject, res) {
    -          res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    -        }))
    -        .then(function (str) {
    -          var body = JSON.parse(str);
    -          assert.equal(body.host, "127.0.0.1:3600");
    -          assert.equal(body.authorization, undefined);
    +      it("drops the header when redirected from a different host via header", function () {
    +        app.get("/a", redirectsTo(302, "http://127.0.0.1:3600/b"));
    +        app.get("/b", function (req, res) {
    +          res.end(JSON.stringify(req.headers));
             });
    +
    +        var opts = url.parse("http://127.0.0.1:3600/a");
    +        opts.headers = { host: "localhost" };
    +        opts.headers[header] = "the header value";
    +
    +        return server.start(app)
    +          .then(asPromise(function (resolve, reject) {
    +            http.get(opts, resolve).on("error", reject);
    +          }))
    +          .then(asPromise(function (resolve, reject, res) {
    +            res.pipe(concat({ encoding: "string" }, resolve)).on("error", reject);
    +          }))
    +          .then(function (str) {
    +            var body = JSON.parse(str);
    +            assert.equal(body.host, "127.0.0.1:3600");
    +            assert.equal(body[header.toLowerCase()], undefined);
    +          });
    +      });
         });
       });
     
    

Vulnerability mechanics

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

References

5

News mentions

0

No linked articles in our index yet.