CVE-2019-18841: Insufficient Information (NVD-CWE-noinfo)
value: Chartkick.js 3.1.0 through 3.1.3, as used in the Chartkick gem before 3.3.0 for Ruby, allows prototype pollution.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 7.3
Published: 2019-11-11T01:15Z
Repository: https://github.com/ankane/chartkick
Language: JavaScript
Files changed: CHANGELOG.md, vendor/assets/javascripts/chartkick.js
Commit date: 2019-11-09 16:18:52 -0800
Commit message: Updated Chartkick.js to 3.2.0

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5317fe9..9e65ca2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,8 @@
 ## 3.3.0 [unreleased]
 
+- Updated Chartkick.js to 3.2.0
 - Rolled back Chart.js to 2.8.0 due to legend change
 
 ## 3.2.2
 
 - Updated Chartkick.js to 3.1.3
diff --git a/vendor/assets/javascripts/chartkick.js b/vendor/assets/javascripts/chartkick.js
index b37b84c..2e25a05 100644
--- a/vendor/assets/javascripts/chartkick.js
+++ b/vendor/assets/javascripts/chartkick.js
@@ -1,35 +1,39 @@
 /*
  * Chartkick.js
  * Create beautiful charts with one line of JavaScript
  * https://github.com/ankane/chartkick.js
- * v3.1.3
+ * v3.2.0
  * MIT License
  */
 
 (function (global, factory) {
   typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
   typeof define === 'function' && define.amd ? define(factory) :
   (global = global || self, global.Chartkick = factory());
-}(this, function () { 'use strict';
+}(this, (function () { 'use strict';
 
   function isArray(variable) {
     return Object.prototype.toString.call(variable) === "[object Array]";
   }
 
   function isFunction(variable) {
     return variable instanceof Function;
   }
 
   function isPlainObject(variable) {
-    return Object.prototype.toString.call(variable) === "[object Object]";
+    // protect against prototype pollution, defense 2
+    return Object.prototype.toString.call(variable) === "[object Object]" && !isFunction(variable) && variable instanceof Object;
   }
 
   // https://github.com/madrobby/zepto/blob/master/src/zepto.js
   function extend(target, source) {
     var key;
     for (key in source) {
+      // protect against prototype pollution, defense 1
+      if (key === "__proto__") { continue; }
+
       if (isPlainObject(source[key]) || isArray(source[key])) {
         if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
           target[key] = {};
         }
         if (isArray(source[key]) && !isArray(target[key])) {
@@ -235,20 +239,72 @@
 
   function isNumber(obj) {
     return typeof obj === "number";
   }
 
-  function formatValue(pre, value, options) {
+  function formatValue(pre, value, options, axis) {
     pre = pre || "";
     if (options.prefix) {
       if (value < 0) {
         value = value * -1;
         pre += "-";
       }
       pre += options.prefix;
     }
 
+    var suffix = options.suffix || "";
+    var precision = options.precision;
+    var round = options.round;
+
+    if (options.byteScale) {
+      var baseValue = axis ? options.byteScale : value;
+      if (baseValue >= 1099511627776) {
+        value /= 1099511627776;
+        suffix = " TB";
+      } else if (baseValue >= 1073741824) {
+        value /= 1073741824;
+        suffix = " GB";
+      } else if (baseValue >= 1048576) {
+        value /= 1048576;
+        suffix = " MB";
+      } else if (baseValue >= 1024) {
+        value /= 1024;
+        suffix = " KB";
+      } else {
+        suffix = " bytes";
+      }
+
+      if (precision === undefined && round === undefined) {
+        precision = 3;
+      }
+    }
+
+    if (precision !== undefined && round !== undefined) {
+      throw Error("Use either round or precision, not both");
+    }
+
+    if (!axis) {
+      if (precision !== undefined) {
+        value = value.toPrecision(precision);
+        if (!options.zeros) {
+          value = parseFloat(value);
+        }
+      }
+
+      if (round !== undefined) {
+        if (round < 0) {
+          var num = Math.pow(10, -1 * round);
+          value = parseInt((1.0 * value / num).toFixed(0)) * num;
+        } else {
+          value = value.toFixed(round);
+          if (!options.zeros) {
+            value = parseFloat(value);
+          }
+        }
+      }
+    }
+
     if (options.thousands || options.decimal) {
       value = toStr(value);
       var parts = value.split(".");
       value = parts[0];
       if (options.thousands) {
@@ -257,11 +313,11 @@
       if (parts.length > 1) {
         value += (options.decimal || ".") + parts[1];
       }
     }
 
-    return pre + value + (options.suffix || "");
+    return pre + value + suffix;
   }
 
   function seriesOption(chart, series, option) {
     if (option in series) {
       return series[option];
@@ -418,22 +474,62 @@
   var setFormatOptions = function(chart, options, chartType) {
     var formatOptions = {
       prefix: chart.options.prefix,
       suffix: chart.options.suffix,
       thousands: chart.options.thousands,
-      decimal: chart.options.decimal
+      decimal: chart.options.decimal,
+      precision: chart.options.precision,
+      round: chart.options.round,
+      zeros: chart.options.zeros
     };
 
+    if (chart.options.bytes) {
+      var series = chart.data;
+      if (chartType === "pie") {
+        series = [{data: series}];
+      }
+
+      // calculate max
+      var max = 0;
+      for (var i = 0; i < series.length; i++) {
+        var s = series[i];
+        for (var j = 0; j < s.data.length; j++) {
+          if (s.data[j][1] > max) {
+            max = s.data[j][1];
+          }
+        }
+      }
+
+      // calculate scale
+      var scale = 1;
+      while (max >= 1024) {
+        scale *= 1024;
+        max /= 1024;
+      }
+
+      // set step size
+      formatOptions.byteScale = scale;
+    }
+
     if (chartType !== "pie") {
       var myAxes = options.scales.yAxes;
       if (chartType === "bar") {
         myAxes = options.scales.xAxes;
       }
 
+      if (formatOptions.byteScale) {
+        if (!myAxes[0].ticks.stepSize) {
+          myAxes[0].ticks.stepSize = formatOptions.byteScale / 2;
+        }
+        if (!myAxes[0].ticks.maxTicksLimit) {
+          myAxes[0].ticks.maxTicksLimit = 4;
+        }
+      }
+
       if (!myAxes[0].ticks.callback) {
         myAxes[0].ticks.callback = function (value) {
-          return formatValue("", value, formatOptions);
+          return formatValue("", value, formatOptions, true);
         };
       }
     }
 
     if (!options.tooltips.callbacks.label) {
@@ -946,11 +1042,14 @@
   var setFormatOptions$1 = function(chart, options, chartType) {
     var formatOptions = {
       prefix: chart.options.prefix,
       suffix: chart.options.suffix,
       thousands: chart.options.thousands,
-      decimal: chart.options.decimal
+      decimal: chart.options.decimal,
+      precision: chart.options.precision,
+      round: chart.options.round,
+      zeros: chart.options.zeros
     };
 
     if (chartType !== "pie" && !options.yAxis.labels.formatter) {
       options.yAxis.labels.formatter = function () {
         return formatValue("", this.value, formatOptions);
@@ -2314,6 +2413,6 @@
   // backwards compatibility for esm require
   Chartkick.default = Chartkick;
 
   return Chartkick;
 
-}));
+})));

Vulnerable code:  * v3.1.3
}(this, function () { 'use strict';
    return Object.prototype.toString.call(variable) === "[object Object]";
  function formatValue(pre, value, options) {
    return pre + value + (options.suffix || "");
      decimal: chart.options.decimal
          return formatValue("", value, formatOptions);
      decimal: chart.options.decimal
}));
Fixed code: - Updated Chartkick.js to 3.2.0
 * v3.2.0
}(this, (function () { 'use strict';
    // protect against prototype pollution, defense 2
    return Object.prototype.toString.call(variable) === "[object Object]" && !isFunction(variable) && variable instanceof Object;
      // protect against prototype pollution, defense 1
      if (key === "__proto__") { continue; }

  function formatValue(pre, value, options, axis) {
    var suffix = options.suffix || "";
    var precision = options.precision;
    var round = options.round;

    if (options.byteScale) {
      var baseValue = axis ? options.byteScale : value;
      if (baseValue >= 1099511627776) {
        value /= 1099511627776;
        suffix = " TB";
      } else if (baseValue >= 1073741824) {
        value /= 1073741824;
        suffix = " GB";
      } else if (baseValue >= 1048576) {
        value /= 1048576;
        suffix = " MB";
      } else if (baseValue >= 1024) {
        value /= 1024;
        suffix = " KB";
      } else {
        suffix = " bytes";
      }

      if (precision === undefined && round === undefined) {
        precision = 3;
      }
    }

    if (precision !== undefined && round !== undefined) {
      throw Error("Use either round or precision, not both");
    }

    if (!axis) {
      if (precision !== undefined) {
        value = value.toPrecision(precision);
        if (!options.zeros) {
          value = parseFloat(value);
        }
      }

      if (round !== undefined) {
        if (round < 0) {
          var num = Math.pow(10, -1 * round);
          value = parseInt((1.0 * value / num).toFixed(0)) * num;
        } else {
          value = value.toFixed(round);
          if (!options.zeros) {
            value = parseFloat(value);
          }
        }
      }
    }

    return pre + value + suffix;
      decimal: chart.options.decimal,
      precision: chart.options.precision,
      round: chart.options.round,
      zeros: chart.options.zeros
    if (chart.options.bytes) {
      var series = chart.data;
      if (chartType === "pie") {
        series = [{data: series}];
      }

      // calculate max
      var max = 0;
      for (var i = 0; i < series.length; i++) {
        var s = series[i];
        for (var j = 0; j < s.data.length; j++) {
          if (s.data[j][1] > max) {
            max = s.data[j][1];
          }
        }
      }

      // calculate scale
      var scale = 1;
      while (max >= 1024) {
        scale *= 1024;
        max /= 1024;
      }

      // set step size
      formatOptions.byteScale = scale;
    }

      if (formatOptions.byteScale) {
        if (!myAxes[0].ticks.stepSize) {
          myAxes[0].ticks.stepSize = formatOptions.byteScale / 2;
        }
        if (!myAxes[0].ticks.maxTicksLimit) {
          myAxes[0].ticks.maxTicksLimit = 4;
        }
      }

          return formatValue("", value, formatOptions, true);
      decimal: chart.options.decimal,
      precision: chart.options.precision,
      round: chart.options.round,
      zeros: chart.options.zeros
})));