Skip to content

Commit 0e68e71

Browse files
mnesporljharb
authored andcommitted
[Fix] Prevent merging __proto__ property
1 parent a689700 commit 0e68e71

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

index.js

+35-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
var hasOwn = Object.prototype.hasOwnProperty;
44
var toStr = Object.prototype.toString;
5+
var defineProperty = Object.defineProperty;
6+
var gOPD = Object.getOwnPropertyDescriptor;
57

68
var isArray = function isArray(arr) {
79
if (typeof Array.isArray === 'function') {
@@ -31,6 +33,35 @@ var isPlainObject = function isPlainObject(obj) {
3133
return typeof key === 'undefined' || hasOwn.call(obj, key);
3234
};
3335

36+
// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target
37+
var setProperty = function setProperty(target, options) {
38+
if (defineProperty && options.name === '__proto__') {
39+
defineProperty(target, options.name, {
40+
enumerable: true,
41+
configurable: true,
42+
value: options.newValue,
43+
writable: true
44+
});
45+
} else {
46+
target[options.name] = options.newValue;
47+
}
48+
};
49+
50+
// Return undefined instead of __proto__ if '__proto__' is not an own property
51+
var getProperty = function getProperty(obj, name) {
52+
if (name === '__proto__') {
53+
if (!hasOwn.call(obj, name)) {
54+
return void 0;
55+
} else if (gOPD) {
56+
// In early versions of node, obj['__proto__'] is buggy when obj has
57+
// __proto__ as an own property. Object.getOwnPropertyDescriptor() works.
58+
return gOPD(obj, name).value;
59+
}
60+
}
61+
62+
return obj[name];
63+
};
64+
3465
module.exports = function extend() {
3566
var options, name, src, copy, copyIsArray, clone;
3667
var target = arguments[0];
@@ -55,8 +86,8 @@ module.exports = function extend() {
5586
if (options != null) {
5687
// Extend the base object
5788
for (name in options) {
58-
src = target[name];
59-
copy = options[name];
89+
src = getProperty(target, name);
90+
copy = getProperty(options, name);
6091

6192
// Prevent never-ending loop
6293
if (target !== copy) {
@@ -70,11 +101,11 @@ module.exports = function extend() {
70101
}
71102

72103
// Never move original objects, clone them
73-
target[name] = extend(deep, clone, copy);
104+
setProperty(target, { name: name, newValue: extend(deep, clone, copy) });
74105

75106
// Don't bring in undefined values
76107
} else if (typeof copy !== 'undefined') {
77-
target[name] = copy;
108+
setProperty(target, { name: name, newValue: copy });
78109
}
79110
}
80111
}

test/index.js

+12
Original file line numberDiff line numberDiff line change
@@ -626,3 +626,15 @@ test('non-object target', function (t) {
626626

627627
t.end();
628628
});
629+
630+
test('__proto__ is merged as an own property', function (t) {
631+
var malicious = { fred: 1 };
632+
Object.defineProperty(malicious, '__proto__', { value: { george: 1 }, enumerable: true });
633+
var target = {};
634+
extend(true, target, malicious);
635+
t.notOk(target.george);
636+
t.ok(Object.prototype.hasOwnProperty.call(target, '__proto__'));
637+
t.deepEqual(Object.getOwnPropertyDescriptor(target, '__proto__').value, { george: 1 });
638+
639+
t.end();
640+
});

0 commit comments

Comments
 (0)