Use Object.create(null) as merge target, to prevent prototype pollution (#7917)

Use Object.create(null) as merge target to prevent polluting `Object.prototype`
This commit is contained in:
Jukka Kurkela
2020-10-18 20:31:01 +03:00
committed by GitHub
parent 88d8ab255f
commit 73b4e82fd5
9 changed files with 31 additions and 16 deletions

View File

@@ -44,8 +44,8 @@ function mergeScaleConfig(config, options) {
const chartDefaults = defaults[config.type] || {scales: {}};
const configScales = options.scales || {};
const chartIndexAxis = getIndexAxis(config.type, options);
const firstIDs = {};
const scales = {};
const firstIDs = Object.create(null);
const scales = Object.create(null);
// First figure out first scale id's per axis.
Object.keys(configScales).forEach(id => {
@@ -53,12 +53,12 @@ function mergeScaleConfig(config, options) {
const axis = determineAxis(id, scaleConf);
const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
firstIDs[axis] = firstIDs[axis] || id;
scales[id] = mergeIf({axis}, [scaleConf, chartDefaults.scales[axis], chartDefaults.scales[defaultId]]);
scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, chartDefaults.scales[axis], chartDefaults.scales[defaultId]]);
});
// Backward compatibility
if (options.scale) {
scales[options.scale.id || 'r'] = mergeIf({axis: 'r'}, [options.scale, chartDefaults.scales.r]);
scales[options.scale.id || 'r'] = mergeIf(Object.create(null), [{axis: 'r'}, options.scale, chartDefaults.scales.r]);
firstIDs.r = firstIDs.r || options.scale.id || 'r';
}
@@ -71,7 +71,7 @@ function mergeScaleConfig(config, options) {
Object.keys(defaultScaleOptions).forEach(defaultID => {
const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
const id = dataset[axis + 'AxisID'] || firstIDs[axis] || axis;
scales[id] = scales[id] || {};
scales[id] = scales[id] || Object.create(null);
mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]);
});
});
@@ -91,7 +91,7 @@ function mergeScaleConfig(config, options) {
* a deep copy of the result, thus doesn't alter inputs.
*/
function mergeConfig(...args/* config objects ... */) {
return merge({}, args, {
return merge(Object.create(null), args, {
merger(key, target, source, options) {
if (key !== 'scales' && key !== 'scale') {
_merger(key, target, source, options);
@@ -118,8 +118,8 @@ function initConfig(config) {
options.scales = scaleConfig;
options.title = (options.title !== false) && merge({}, [defaults.plugins.title, options.title]);
options.tooltips = (options.tooltips !== false) && merge({}, [defaults.plugins.tooltip, options.tooltips]);
options.title = (options.title !== false) && merge(Object.create(null), [defaults.plugins.title, options.title]);
options.tooltips = (options.tooltips !== false) && merge(Object.create(null), [defaults.plugins.tooltip, options.tooltips]);
return config;
}

View File

@@ -334,7 +334,7 @@ export default class DatasetController {
*/
configure() {
const me = this;
me._config = merge({}, [
me._config = merge(Object.create(null), [
me.chart.options[me._type].datasets,
me.getDataset(),
], {

View File

@@ -12,7 +12,7 @@ function getScope(node, key) {
const keys = key.split('.');
for (let i = 0, n = keys.length; i < n; ++i) {
const k = keys[i];
node = node[k] || (node[k] = {});
node = node[k] || (node[k] = Object.create(null));
}
return node;
}

View File

@@ -1,5 +1,5 @@
import defaults from './core.defaults';
import {each} from '../helpers/helpers.core';
import {each, isObject} from '../helpers/helpers.core';
import {toPadding} from '../helpers/helpers.options';
/**
@@ -84,6 +84,10 @@ function updateDims(chartArea, params, layout) {
const box = layout.box;
const maxPadding = chartArea.maxPadding;
if (isObject(layout.pos)) {
// dynamically placed boxes are not considered
return;
}
if (layout.size) {
// this layout was already counted for, lets first reduce old size
chartArea[layout.pos] -= layout.size;

View File

@@ -157,7 +157,7 @@ export function clone(source) {
}
if (isObject(source)) {
const target = {};
const target = Object.create(null);
const keys = Object.keys(source);
const klen = keys.length;
let k = 0;

View File

@@ -55,11 +55,14 @@ function parseFillOption(line) {
*/
function decodeFill(line, index, count) {
const fill = parseFillOption(line);
let target = parseFloat(fill);
if (isObject(fill)) {
return isNaN(fill.value) ? false : fill;
} else if (isFinite(target) && Math.floor(target) === target) {
}
let target = parseFloat(fill);
if (isFinite(target) && Math.floor(target) === target) {
if (fill[0] === '-' || fill[0] === '+') {
target = index + target;
}

View File

@@ -629,7 +629,7 @@ export class Legend extends Element {
}
function resolveOptions(options) {
return options !== false && merge({}, [defaults.plugins.legend, options]);
return options !== false && merge(Object.create(null), [defaults.plugins.legend, options]);
}
function createNewLegendAndAttach(chart, legendOpts) {

View File

@@ -139,7 +139,7 @@ function createTooltipItem(chart, item) {
*/
function resolveOptions(options, fallbackFont) {
options = merge({}, [defaults.plugins.tooltip, options]);
options = merge(Object.create(null), [defaults.plugins.tooltip, options]);
options.bodyFont = toFont(options.bodyFont, fallbackFont);
options.titleFont = toFont(options.titleFont, fallbackFont);

View File

@@ -13,4 +13,12 @@ describe('Core helper tests', function() {
expect(helpers.uid()).toBe(uid + 2);
expect(helpers.uid()).toBe(uid + 3);
});
describe('clone', function() {
it('should not allow prototype pollution', function() {
const test = helpers.clone(JSON.parse('{"__proto__":{"polluted": true}}'));
expect(test.prototype).toBeUndefined();
expect(Object.prototype.polluted).toBeUndefined();
});
});
});