Skip to content

Commit 7859e0e

Browse files
authoredJan 26, 2021
Merge pull request #60 from deleonio/fix/vulnerability-prototype-pollution
Fix Vulnerability - Ready
2 parents a123018 + 49ce1f4 commit 7859e0e

8 files changed

+15754
-51
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ components
1616
node_modules
1717
npm-debug.log
1818

19+
.nyc_output/
1920
coverage/
2021

2122
pathval.js

‎.travis.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ cache:
1010
- node_modules
1111

1212
node_js:
13-
- 0.10 # to be removed 2016-10-01
14-
- 0.12 # to be removed 2016-12-31
15-
- 4 # to be removed 2018-04-01
16-
- 6 # to be removed 2019-04-01
13+
# - 0.10 # to be removed 2016-10-01
14+
# - 0.12 # to be removed 2016-12-31
15+
# - 4 # to be removed 2018-04-01
16+
# - 6 # to be removed 2019-04-01
1717
- lts/* # safety net; don't remove
1818
- node # safety net; don't remove
1919

‎index.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,20 @@ function parsePath(path) {
7676
var str = path.replace(/([^\\])\[/g, '$1.[');
7777
var parts = str.match(/(\\\.|[^.]+?)+/g);
7878
return parts.map(function mapMatches(value) {
79+
if (
80+
value === 'constructor' ||
81+
value === '__proto__' ||
82+
value === 'prototype'
83+
) {
84+
return {};
85+
}
7986
var regexp = /^\[(\d+)\]$/;
8087
var mArr = regexp.exec(value);
8188
var parsed = null;
8289
if (mArr) {
8390
parsed = { i: parseFloat(mArr[1]) };
8491
} else {
85-
parsed = { p: value.replace(/\\([.\[\]])/g, '$1') };
92+
parsed = { p: value.replace(/\\([.[\]])/g, '$1') };
8693
}
8794

8895
return parsed;
@@ -107,7 +114,7 @@ function parsePath(path) {
107114
function internalGetPathValue(obj, parsed, pathDepth) {
108115
var temporaryValue = obj;
109116
var res = null;
110-
pathDepth = (typeof pathDepth === 'undefined' ? parsed.length : pathDepth);
117+
pathDepth = typeof pathDepth === 'undefined' ? parsed.length : pathDepth;
111118

112119
for (var i = 0; i < pathDepth; i++) {
113120
var part = parsed[i];
@@ -118,7 +125,7 @@ function internalGetPathValue(obj, parsed, pathDepth) {
118125
temporaryValue = temporaryValue[part.p];
119126
}
120127

121-
if (i === (pathDepth - 1)) {
128+
if (i === pathDepth - 1) {
122129
res = temporaryValue;
123130
}
124131
}
@@ -152,7 +159,7 @@ function internalSetPathValue(obj, val, parsed) {
152159
part = parsed[i];
153160

154161
// If it's the last part of the path, we set the 'propName' value with the property name
155-
if (i === (pathDepth - 1)) {
162+
if (i === pathDepth - 1) {
156163
propName = typeof part.p === 'undefined' ? part.i : part.p;
157164
// Now we set the property with the name held by 'propName' on object with the desired val
158165
tempObj[propName] = val;
@@ -199,7 +206,10 @@ function getPathInfo(obj, path) {
199206
var parsed = parsePath(path);
200207
var last = parsed[parsed.length - 1];
201208
var info = {
202-
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,
203213
name: last.p || last.i,
204214
value: internalGetPathValue(obj, parsed),
205215
};

‎karma.conf.js

+12-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
/* eslint no-process-env: "off" */
2+
13
'use strict';
4+
25
var packageJson = require('./package.json');
36
var defaultTimeout = 120000;
47
var browserifyIstanbul = require('browserify-istanbul');
58
module.exports = function configureKarma(config) {
6-
var localBrowsers = [
7-
'PhantomJS',
8-
];
9+
var localBrowsers = [ 'PhantomJS' ];
910
var sauceLabsBrowsers = {
1011
SauceChromeLatest: {
1112
base: 'SauceLabs',
@@ -41,7 +42,9 @@ module.exports = function configureKarma(config) {
4142
config.set({
4243
basePath: '',
4344
browsers: localBrowsers,
44-
logLevel: process.env.npm_config_debug ? config.LOG_DEBUG : config.LOG_INFO,
45+
logLevel: process.env.npm_config_debug ?
46+
config.LOG_DEBUG :
47+
config.LOG_INFO,
4548
frameworks: [ 'browserify', 'mocha' ],
4649
files: [ 'test/*.js' ],
4750
exclude: [],
@@ -51,9 +54,7 @@ module.exports = function configureKarma(config) {
5154
browserify: {
5255
debug: true,
5356
bare: true,
54-
transform: [
55-
browserifyIstanbul({ ignore: [ '**/node_modules/**', '**/test/**' ] }),
56-
],
57+
transform: [ browserifyIstanbul({ ignore: [ '**/node_modules/**', '**/test/**' ] }) ],
5758
},
5859
reporters: [ 'progress', 'coverage' ],
5960
coverageReporter: {
@@ -82,14 +83,11 @@ module.exports = function configureKarma(config) {
8283
browsers: localBrowsers.concat(Object.keys(sauceLabsBrowsers)),
8384
sauceLabs: {
8485
testName: packageJson.name,
85-
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER || new Date().getTime(),
86+
tunnelIdentifier:
87+
process.env.TRAVIS_JOB_NUMBER || new Date().getTime(),
8688
recordVideo: true,
87-
startConnect: ('TRAVIS' in process.env) === false,
88-
tags: [
89-
'pathval_' + packageJson.version,
90-
process.env.SAUCE_USERNAME + '@' + branch,
91-
build,
92-
],
89+
startConnect: 'TRAVIS' in process.env === false,
90+
tags: [ 'pathval_' + packageJson.version, process.env.SAUCE_USERNAME + '@' + branch, build ],
9391
},
9492
});
9593
}

‎package-lock.json

+15,670
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+23-22
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@
1919
"url": "git+ssh://git@github.com/chaijs/pathval.git"
2020
},
2121
"scripts": {
22-
"build": "browserify --bare $npm_package_main --standalone pathval -o pathval.js",
22+
"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",
2728
"test": "npm run test:node && npm run test:browser && npm run upload-coverage",
2829
"test:browser": "karma start --singleRun=true",
29-
"test:node": "istanbul cover _mocha",
30+
"test:node": "nyc mocha",
3031
"upload-coverage": "lcov-result-merger 'coverage/**/lcov.info' | coveralls; exit 0"
3132
},
3233
"config": {
@@ -50,27 +51,27 @@
5051
}
5152
},
5253
"devDependencies": {
53-
"browserify": "^13.0.0",
54-
"browserify-istanbul": "^1.0.0",
55-
"coveralls": "2.11.9",
56-
"eslint": "^2.4.0",
57-
"eslint-config-strict": "^8.5.0",
58-
"eslint-plugin-filenames": "^0.2.0",
59-
"ghooks": "^1.0.1",
60-
"istanbul": "^0.4.2",
61-
"karma": "^0.13.22",
62-
"karma-browserify": "^5.0.2",
63-
"karma-coverage": "^0.5.5",
64-
"karma-mocha": "^0.2.2",
65-
"karma-phantomjs-launcher": "^1.0.0",
66-
"karma-sauce-launcher": "^0.3.1",
67-
"lcov-result-merger": "^1.0.2",
68-
"mocha": "^3.1.2",
69-
"phantomjs-prebuilt": "^2.1.5",
70-
"semantic-release": "^4.3.5",
54+
"browserify": "^17.0.0",
55+
"browserify-istanbul": "^3.0.1",
56+
"coveralls": "^3.1.0",
57+
"eslint": "^7.13.0",
58+
"eslint-config-strict": "^14.0.1",
59+
"eslint-plugin-filenames": "^1.3.2",
60+
"ghooks": "^2.0.4",
61+
"karma": "^5.2.3",
62+
"karma-browserify": "^7.0.0",
63+
"karma-coverage": "^2.0.3",
64+
"karma-mocha": "^2.0.1",
65+
"karma-phantomjs-launcher": "^1.0.4",
66+
"karma-sauce-launcher": "^4.3.3",
67+
"lcov-result-merger": "^3.1.0",
68+
"mocha": "^8.2.1",
69+
"nyc": "^15.1.0",
70+
"phantomjs-prebuilt": "^2.1.16",
71+
"semantic-release": "^17.2.2",
7172
"simple-assert": "^1.0.0",
72-
"travis-after-all": "^1.4.4",
73-
"validate-commit-msg": "^2.3.1"
73+
"travis-after-all": "^1.4.5",
74+
"validate-commit-msg": "^2.14.0"
7475
},
7576
"engines": {
7677
"node": "*"

‎test/.eslintrc

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"mocha": true
88
},
99
"rules": {
10+
"no-process-env": "off",
1011
"no-new-wrappers": 0,
1112
"no-array-constructor": 0,
1213
"no-new-object": 0,

‎test/index.js

+28-6
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,7 @@ describe('getPathValue', function () {
121121
hello: 'world',
122122
},
123123
world: [ 'hello', 'universe' ],
124-
complex: [
125-
{ hello: 'universe' },
126-
{ universe: 'world' },
127-
[ { hello: 'world' } ],
128-
],
124+
complex: [ { hello: 'universe' }, { universe: 'world' }, [ { hello: 'world' } ] ],
129125
};
130126

131127
var arr = [ [ true ] ];
@@ -171,7 +167,7 @@ describe('setPathValue', function () {
171167
});
172168

173169
it('allows value to be set in complex object', function () {
174-
var obj = { hello: { } };
170+
var obj = { hello: {} };
175171
pathval.setPathValue(obj, 'hello.universe', 42);
176172
assert(obj.hello.universe === 42);
177173
});
@@ -222,4 +218,30 @@ describe('setPathValue', function () {
222218
var valueReturned = pathval.setPathValue(obj, 'hello[2]', 3);
223219
assert(obj === valueReturned);
224220
});
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+
225247
});

0 commit comments

Comments
 (0)
Please sign in to comment.