Skip to content

Commit c77b9d2

Browse files
committed
fix: prototype pollution vulnerability + working tests
1 parent 49031e4 commit c77b9d2

File tree

3 files changed

+41
-5
lines changed

3 files changed

+41
-5
lines changed

index.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict';
22

3-
43
/* !
54
* Chai - pathval utility
65
* Copyright(c) 2012-2014 Jake Luer <[email protected]>
@@ -77,6 +76,13 @@ function parsePath(path) {
7776
var str = path.replace(/([^\\])\[/g, '$1.[');
7877
var parts = str.match(/(\\\.|[^.]+?)+/g);
7978
return parts.map(function mapMatches(value) {
79+
if (
80+
value === 'constructor' ||
81+
value === '__proto__' ||
82+
value === 'prototype'
83+
) {
84+
return {};
85+
}
8086
var regexp = /^\[(\d+)\]$/;
8187
var mArr = regexp.exec(value);
8288
var parsed = null;
@@ -108,7 +114,7 @@ function parsePath(path) {
108114
function internalGetPathValue(obj, parsed, pathDepth) {
109115
var temporaryValue = obj;
110116
var res = null;
111-
pathDepth = (typeof pathDepth === 'undefined' ? parsed.length : pathDepth);
117+
pathDepth = typeof pathDepth === 'undefined' ? parsed.length : pathDepth;
112118

113119
for (var i = 0; i < pathDepth; i++) {
114120
var part = parsed[i];
@@ -119,7 +125,7 @@ function internalGetPathValue(obj, parsed, pathDepth) {
119125
temporaryValue = temporaryValue[part.p];
120126
}
121127

122-
if (i === (pathDepth - 1)) {
128+
if (i === pathDepth - 1) {
123129
res = temporaryValue;
124130
}
125131
}
@@ -153,7 +159,7 @@ function internalSetPathValue(obj, val, parsed) {
153159
part = parsed[i];
154160

155161
// If it's the last part of the path, we set the 'propName' value with the property name
156-
if (i === (pathDepth - 1)) {
162+
if (i === pathDepth - 1) {
157163
propName = typeof part.p === 'undefined' ? part.i : part.p;
158164
// Now we set the property with the name held by 'propName' on object with the desired val
159165
tempObj[propName] = val;
@@ -200,7 +206,10 @@ function getPathInfo(obj, path) {
200206
var parsed = parsePath(path);
201207
var last = parsed[parsed.length - 1];
202208
var info = {
203-
parent: parsed.length > 1 ? internalGetPathValue(obj, parsed, parsed.length - 1) : obj,
209+
parent:
210+
parsed.length > 1 ?
211+
internalGetPathValue(obj, parsed, parsed.length - 1) :
212+
obj,
204213
name: last.p || last.i,
205214
value: internalGetPathValue(obj, parsed),
206215
};

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"scripts": {
2222
"build": "browserify --standalone pathval -o pathval.js",
2323
"lint": "eslint --ignore-path .gitignore .",
24+
"lint:fix": "npm run lint -- --fix",
2425
"prepublish": "npm run build",
2526
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
2627
"pretest": "npm run lint",

test/index.js

+26
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,30 @@ describe('setPathValue', function () {
218218
var valueReturned = pathval.setPathValue(obj, 'hello[2]', 3);
219219
assert(obj === valueReturned);
220220
});
221+
222+
describe('fix prototype pollution vulnerability', function () {
223+
224+
it('exclude constructor', function () {
225+
var obj = {};
226+
assert(typeof obj.constructor === 'function'); // eslint-disable-line
227+
pathval.setPathValue(obj, 'constructor', null);
228+
assert(typeof obj.constructor === 'function'); // eslint-disable-line
229+
});
230+
231+
it('exclude __proto__', function () {
232+
var obj = {};
233+
assert(typeof polluted === 'undefined'); // eslint-disable-line
234+
pathval.setPathValue(obj, '__proto__.polluted', true);
235+
assert(typeof polluted === 'undefined'); // eslint-disable-line
236+
});
237+
238+
it('exclude prototype', function () {
239+
var obj = {};
240+
assert(typeof obj.prototype === 'undefined'); // eslint-disable-line
241+
pathval.setPathValue(obj, 'prototype', true);
242+
assert(typeof obj.prototype === 'undefined'); // eslint-disable-line
243+
});
244+
245+
});
246+
221247
});

0 commit comments

Comments
 (0)