From d63002dbc5ac2bc89c264a81bfb72769ec924f1b Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 30 Oct 2018 16:18:51 -0700 Subject: [PATCH] [Fix] Prevent merging __proto__ property --- .eslintrc | 1 + lib/extend.js | 41 +++++++++++++++++++++++++++++++++++++---- package.json | 3 ++- test/index.js | 12 ++++++++++++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/.eslintrc b/.eslintrc index 2ca6e7a..5815b7f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,6 +9,7 @@ "eqeqeq": [2, "allow-null"], "indent": [2, 2], "max-depth": [2, 5], + "max-params": [2, 3], "max-statements": [2, 29], "multiline-comment-style": 0, "no-continue": [1], diff --git a/lib/extend.js b/lib/extend.js index d437caf..31c0c5e 100644 --- a/lib/extend.js +++ b/lib/extend.js @@ -10,6 +10,39 @@ * Port of jQuery.extend that actually works on node.js */ var is = require('is'); +var has = require('has'); + +var defineProperty = Object.defineProperty; +var gOPD = Object.getOwnPropertyDescriptor; + +// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target +var setProperty = function setP(target, name, value) { + if (defineProperty && name === '__proto__') { + defineProperty(target, name, { + enumerable: true, + configurable: true, + value: value, + writable: true + }); + } else { + target[name] = value; + } +}; + +// Return undefined instead of __proto__ if '__proto__' is not an own property +var getProperty = function getP(obj, name) { + if (name === '__proto__') { + if (!has(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]; +}; // eslint-disable-next-line func-style function extend() { @@ -41,8 +74,8 @@ function extend() { } // 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) { @@ -59,11 +92,11 @@ function extend() { } // Never move original objects, clone them - target[name] = extend(deep, clone, copy); + setProperty(target, name, extend(deep, clone, copy)); // Don't bring in undefined values } else if (typeof copy !== 'undefined') { - target[name] = copy; + setProperty(target, name, copy); } } } diff --git a/package.json b/package.json index 8298a27..52178a9 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ ], "author": "dreamerslab ", "dependencies": { - "is": "^3.1.0" + "has": "^1.0.3", + "is": "^3.2.1" }, "devDependencies": { "@ljharb/eslint-config": "^13.0.0", diff --git a/test/index.js b/test/index.js index 2386469..d57c2ff 100644 --- a/test/index.js +++ b/test/index.js @@ -571,3 +571,15 @@ test('deep clone; arrays are merged', function (t) { t.deepEqual(target, expectedTarget, 'arrays are merged'); t.end(); }); + +test('__proto__ is merged as an own property', { skip: !Object.defineProperty }, 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(); +});