Skip to content

Commit d63002d

Browse files
committed
[Fix] Prevent merging __proto__ property
1 parent c8194ae commit d63002d

File tree

4 files changed

+52
-5
lines changed

4 files changed

+52
-5
lines changed

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"eqeqeq": [2, "allow-null"],
1010
"indent": [2, 2],
1111
"max-depth": [2, 5],
12+
"max-params": [2, 3],
1213
"max-statements": [2, 29],
1314
"multiline-comment-style": 0,
1415
"no-continue": [1],

lib/extend.js

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,39 @@
1010
* Port of jQuery.extend that actually works on node.js
1111
*/
1212
var is = require('is');
13+
var has = require('has');
14+
15+
var defineProperty = Object.defineProperty;
16+
var gOPD = Object.getOwnPropertyDescriptor;
17+
18+
// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target
19+
var setProperty = function setP(target, name, value) {
20+
if (defineProperty && name === '__proto__') {
21+
defineProperty(target, name, {
22+
enumerable: true,
23+
configurable: true,
24+
value: value,
25+
writable: true
26+
});
27+
} else {
28+
target[name] = value;
29+
}
30+
};
31+
32+
// Return undefined instead of __proto__ if '__proto__' is not an own property
33+
var getProperty = function getP(obj, name) {
34+
if (name === '__proto__') {
35+
if (!has(obj, name)) {
36+
return void 0;
37+
} else if (gOPD) {
38+
// In early versions of node, obj['__proto__'] is buggy when obj has
39+
// __proto__ as an own property. Object.getOwnPropertyDescriptor() works.
40+
return gOPD(obj, name).value;
41+
}
42+
}
43+
44+
return obj[name];
45+
};
1346

1447
// eslint-disable-next-line func-style
1548
function extend() {
@@ -41,8 +74,8 @@ function extend() {
4174
}
4275
// Extend the base object
4376
for (name in options) {
44-
src = target[name];
45-
copy = options[name];
77+
src = getProperty(target, name);
78+
copy = getProperty(options, name);
4679

4780
// Prevent never-ending loop
4881
if (target === copy) {
@@ -59,11 +92,11 @@ function extend() {
5992
}
6093

6194
// Never move original objects, clone them
62-
target[name] = extend(deep, clone, copy);
95+
setProperty(target, name, extend(deep, clone, copy));
6396

6497
// Don't bring in undefined values
6598
} else if (typeof copy !== 'undefined') {
66-
target[name] = copy;
99+
setProperty(target, name, copy);
67100
}
68101
}
69102
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
],
1313
"author": "dreamerslab <ben@dreamerslab.com>",
1414
"dependencies": {
15-
"is": "^3.1.0"
15+
"has": "^1.0.3",
16+
"is": "^3.2.1"
1617
},
1718
"devDependencies": {
1819
"@ljharb/eslint-config": "^13.0.0",

test/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,3 +571,15 @@ test('deep clone; arrays are merged', function (t) {
571571
t.deepEqual(target, expectedTarget, 'arrays are merged');
572572
t.end();
573573
});
574+
575+
test('__proto__ is merged as an own property', { skip: !Object.defineProperty }, function (t) {
576+
var malicious = { fred: 1 };
577+
Object.defineProperty(malicious, '__proto__', { value: { george: 1 }, enumerable: true });
578+
var target = {};
579+
extend(true, target, malicious);
580+
t.notOk(target.george);
581+
t.ok(Object.prototype.hasOwnProperty.call(target, '__proto__'));
582+
t.deepEqual(Object.getOwnPropertyDescriptor(target, '__proto__').value, { george: 1 });
583+
584+
t.end();
585+
});

0 commit comments

Comments
 (0)