CVE-2019-18841
Description
Chartkick.js 3.1.0 through 3.1.3, as used in the Chartkick gem before 3.3.0 for Ruby, allows prototype pollution.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Chartkick.js 3.1.0 through 3.1.3 allows prototype pollution, enabling attackers to inject arbitrary properties into an Object prototype.
Vulnerability
CVE-2019-18841 is a prototype pollution vulnerability affecting Chartkick.js versions 3.1.0 through 3.1.3, which are used in the Chartkick gem for Ruby before version 3.3.0 [1]. The issue resides in the extend function and the isPlainObject check within the JavaScript library. Specifically, the extend function did not filter out the __proto__ key when copying properties from a source object to a target object, and the isPlainObject check was insufficient to prevent prototype pollution via crafted objects [4].
Exploitation
An attacker can exploit this vulnerability by supplying a specially crafted JSON payload that includes a __proto__ property. When Chartkick processes this data (e.g., chart data from user input, API responses, or other untrusted sources), the __proto__ key is not blocked, allowing the attacker to inject arbitrary properties into the global Object.prototype [4]. The attack does not require authentication if the attacker can control data passed to Chartkick, which may be common in web applications that render charts from user-supplied data. The fix added two defenses: a check for __proto__ in the extend loop and an improved isPlainObject validation that excludes functions [4].
Impact
Successful prototype pollution can lead to severe consequences, including denial of service, property injection, and in some contexts, arbitrary code execution. By polluting Object.prototype, an attacker can alter the behavior of all objects in the application, potentially bypassing security checks, modifying default settings, or triggering unanticipated code paths. The severity is elevated because Chartkick is widely used in Ruby on Rails applications for creating JavaScript charts, and the polluted prototype can affect both client-side and server-side JavaScript execution if Node.js is involved.
Mitigation
The vulnerability was patched in Chartkick gem version 3.3.0 and Chartkick.js version 3.2.0 [3][4]. Users should upgrade to Chartkick gem 3.3.0 or later, which ships with the fixed Chartkick.js. No known workarounds exist; upgrading is the recommended action. The issue is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog as of this writing.
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.
| Package | Affected versions | Patched versions |
|---|---|---|
chartkickRubyGems | < 3.3.0 | 3.3.0 |
chartkicknpm | >= 3.1.0, < 3.2.0 | 3.2.0 |
Affected products
2- ghsa-coords2 versions
< 3.3.0+ 1 more
- (no CPE)range: < 3.3.0
- (no CPE)range: >= 3.1.0, < 3.2.0
Patches
1b810936bbf68Updated Chartkick.js to 3.2.0
2 files changed · +109 −9
CHANGELOG.md+1 −0 modified@@ -1,5 +1,6 @@ ## 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
vendor/assets/javascripts/chartkick.js+108 −9 modified@@ -2,15 +2,15 @@ * 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]"; @@ -21,13 +21,17 @@ } 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] = {}; @@ -237,7 +241,7 @@ return typeof obj === "number"; } - function formatValue(pre, value, options) { + function formatValue(pre, value, options, axis) { pre = pre || ""; if (options.prefix) { if (value < 0) { @@ -247,6 +251,58 @@ 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("."); @@ -259,7 +315,7 @@ } } - return pre + value + (options.suffix || ""); + return pre + value + suffix; } function seriesOption(chart, series, option) { @@ -420,18 +476,58 @@ 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); }; } } @@ -948,7 +1044,10 @@ 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) { @@ -2316,4 +2415,4 @@ return Chartkick; -})); +})));
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- github.com/advisories/GHSA-5pm8-492c-92p5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-18841ghsaADVISORY
- chartkick.comghsax_refsource_MISCWEB
- github.com/ankane/chartkick.js/issues/117ghsax_refsource_MISCWEB
- github.com/ankane/chartkick/blob/master/CHANGELOG.mdghsax_refsource_MISCWEB
- github.com/ankane/chartkick/commit/b810936bbf687bc74c5b6dba72d2397a399885faghsax_refsource_CONFIRMWEB
- github.com/ankane/chartkick/commits/masterghsax_refsource_MISCWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/chartkick/CVE-2019-18841.ymlghsaWEB
- rubygems.org/gems/chartkickghsaWEB
- rubygems.org/gems/chartkick/mitrex_refsource_MISC
News mentions
0No linked articles in our index yet.