Vulnerability: Cookie exposure in requestretry Exposure of Sensitive Information to an Unauthorized Actor in GitHub repository fgribreau/node-request-retry prior to 7.0.0 via cookies being leaked to external sites.
Vulnerability ID: GHSA-hjp8-2cm3-cc45
CWE: CWE-200
Severity score: 7.5
Project: https://github.com/fgribreau/node-request-retry
Published: 2022-02-24
File: index.js
Commit message: Prevent Cookie & Authorization Headers from being forwarded when the URL redirects to another domain (information leak) #137
Commit author: Timothee Desurmont

Vulnerable version (before fix):
 var extend = require('extend'); var request = require('request'); var RetryStrategies = require('./strategies'); var _ = require('lodash');  var DEFAULTS = {   maxAttempts: 5, // try 5 times function defaultPromiseFactory(resolver) {   return new Promise(resolver); }  function _cloneOptions(options) {   const cloned = {};   for (let key in options) { function Request(url, options, f, retryConfig) {    * Option object    * @type {Object}    */   this.options = options;    /**    * Return true if the request should be retried

Fixed version (after fix):
 var extend = require('extend'); var request = require('request'); var RetryStrategies = require('./strategies'); var _ = require('lodash'); var url = require('url'); var querystring = require("querystring");  var DEFAULTS = {   maxAttempts: 5, // try 5 times function defaultPromiseFactory(resolver) {   return new Promise(resolver); }  // Prevent Cookie & Authorization Headers from being forwarded  // when the URL redirects to another domain (information leak) #137  function sanitizeHeaders(options) {      const HEADERS_TO_IGNORE = ["cookie", "authorization"];    const urlObject = url.parse(options.url)   const queryObject = querystring.parse(urlObject.query);      const hasExternalLink = Object.keys(queryObject).reduce(function(acc, cur) {          let qUrl = url.parse(queryObject[cur]);      // external link if protocol || host || port is different     if(!!qUrl.host && (qUrl.protocol !== urlObject.protocol || qUrl.host !== urlObject.host || qUrl.port !== urlObject.port) ) {       acc = true;     }          return acc;    }, false);    if (hasExternalLink && options.hasOwnProperty("headers") && typeof(options.headers) === "object") {          // if External Link: remove Cookie and Authorization from Headers     Object.keys(options.headers).filter(function(key) {       return HEADERS_TO_IGNORE.includes(key.toLowerCase())     }).map(function(key) {       return delete options.headers[key]     });    }    return options; }  function _cloneOptions(options) {   const cloned = {};   for (let key in options) { function Request(url, options, f, retryConfig) {    * Option object    * @type {Object}    */   this.options = sanitizeHeaders(options);    /**    * Return true if the request should be retried

Patch:
patch: @@ -11,6 +11,8 @@ var extend = require('extend');
 var request = require('request');
 var RetryStrategies = require('./strategies');
 var _ = require('lodash');
+var url = require('url');
+var querystring = require("querystring");
 
 var DEFAULTS = {
   maxAttempts: 5, // try 5 times
@@ -24,6 +26,42 @@ function defaultPromiseFactory(resolver) {
   return new Promise(resolver);
 }
 
+// Prevent Cookie & Authorization Headers from being forwarded 
+// when the URL redirects to another domain (information leak) #137 
+function sanitizeHeaders(options) {
+  
+  const HEADERS_TO_IGNORE = ["cookie", "authorization"];
+
+  const urlObject = url.parse(options.url)
+  const queryObject = querystring.parse(urlObject.query);
+  
+  const hasExternalLink = Object.keys(queryObject).reduce(function(acc, cur) {
+    
+    let qUrl = url.parse(queryObject[cur]);
+
+    // external link if protocol || host || port is different
+    if(!!qUrl.host && (qUrl.protocol !== urlObject.protocol || qUrl.host !== urlObject.host || qUrl.port !== urlObject.port) ) {
+      acc = true;
+    }
+    
+    return acc;
+
+  }, false);
+
+  if (hasExternalLink && options.hasOwnProperty("headers") && typeof(options.headers) === "object") {
+    
+    // if External Link: remove Cookie and Authorization from Headers
+    Object.keys(options.headers).filter(function(key) {
+      return HEADERS_TO_IGNORE.includes(key.toLowerCase())
+    }).map(function(key) {
+      return delete options.headers[key]
+    });
+
+  }
+
+  return options;
+}
+
 function _cloneOptions(options) {
   const cloned = {};
   for (let key in options) {
@@ -85,7 +123,7 @@ function Request(url, options, f, retryConfig) {
    * Option object
    * @type {Object}
    */
-  this.options = options;
+  this.options = sanitizeHeaders(options);
 
   /**
    * Return true if the request should be retried
path: index.js