From 0e68e71d93507fcc391e398bc84abd0666b28190 Mon Sep 17 00:00:00 2001 From: mnespor Date: Mon, 23 Apr 2018 21:32:39 -0500 Subject: [PATCH] [Fix] Prevent merging __proto__ property --- index.js | 39 +++++++++++++++++++++++++++++++++++---- test/index.js | 12 ++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index bbe53f6..2aa3faa 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,8 @@ var hasOwn = Object.prototype.hasOwnProperty; var toStr = Object.prototype.toString; +var defineProperty = Object.defineProperty; +var gOPD = Object.getOwnPropertyDescriptor; var isArray = function isArray(arr) { if (typeof Array.isArray === 'function') { @@ -31,6 +33,35 @@ var isPlainObject = function isPlainObject(obj) { return typeof key === 'undefined' || hasOwn.call(obj, key); }; +// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target +var setProperty = function setProperty(target, options) { + if (defineProperty && options.name === '__proto__') { + defineProperty(target, options.name, { + enumerable: true, + configurable: true, + value: options.newValue, + writable: true + }); + } else { + target[options.name] = options.newValue; + } +}; + +// Return undefined instead of __proto__ if '__proto__' is not an own property +var getProperty = function getProperty(obj, name) { + if (name === '__proto__') { + if (!hasOwn.call(obj, name)) { + return void 0; + } else if (gOPD) { + // In early versions of node, obj['__proto__'] is buggy when obj has + // __proto__ as an own property. Object.getOwnPropertyDescriptor() works. + return gOPD(obj, name).value; + } + } + + return obj[name]; +}; + module.exports = function extend() { var options, name, src, copy, copyIsArray, clone; var target = arguments[0]; @@ -55,8 +86,8 @@ module.exports = function extend() { if (options != null) { // Extend the base object for (name in options) { - src = target[name]; - copy = options[name]; + src = getProperty(target, name); + copy = getProperty(options, name); // Prevent never-ending loop if (target !== copy) { @@ -70,11 +101,11 @@ module.exports = function extend() { } // Never move original objects, clone them - target[name] = extend(deep, clone, copy); + setProperty(target, { name: name, newValue: extend(deep, clone, copy) }); // Don't bring in undefined values } else if (typeof copy !== 'undefined') { - target[name] = copy; + setProperty(target, { name: name, newValue: copy }); } } } diff --git a/test/index.js b/test/index.js index 1a5c087..a57e435 100644 --- a/test/index.js +++ b/test/index.js @@ -626,3 +626,15 @@ test('non-object target', function (t) { t.end(); }); + +test('__proto__ is merged as an own property', function (t) { + var malicious = { fred: 1 }; + Object.defineProperty(malicious, '__proto__', { value: { george: 1 }, enumerable: true }); + var target = {}; + extend(true, target, malicious); + t.notOk(target.george); + t.ok(Object.prototype.hasOwnProperty.call(target, '__proto__')); + t.deepEqual(Object.getOwnPropertyDescriptor(target, '__proto__').value, { george: 1 }); + + t.end(); +});