Skip to content

Commit 4774207

Browse files
authored
fix: back-porting prototype fixes for *really* old version (#271)
1 parent 2c95ba9 commit 4774207

File tree

4 files changed

+71
-39
lines changed

4 files changed

+71
-39
lines changed

index.js

+31-37
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ var camelCase = require('camelcase')
22
var path = require('path')
33
var tokenizeArgString = require('./lib/tokenize-arg-string')
44
var util = require('util')
5+
var assign = require('object.assign')
56

67
function parse (args, opts) {
7-
if (!opts) opts = {}
8+
if (!opts) opts = Object.create(null)
89
// allow a string argument to be passed in rather
910
// than an argv array.
1011
args = tokenizeArgString(args)
1112
// aliases might have transitive relationships, normalize this.
12-
var aliases = combineAliases(opts.alias || {})
13+
var aliases = combineAliases(opts.alias || Object.create(null))
1314
var configuration = assign({
1415
'short-option-groups': true,
1516
'camel-case-expansion': true,
@@ -19,27 +20,27 @@ function parse (args, opts) {
1920
'duplicate-arguments-array': true,
2021
'flatten-duplicate-arrays': true
2122
}, opts.configuration)
22-
var defaults = opts.default || {}
23+
var defaults = opts.default || Object.create(null)
2324
var configObjects = opts.configObjects || []
2425
var envPrefix = opts.envPrefix
25-
var newAliases = {}
26+
var newAliases = Object.create(null)
2627
// allow a i18n handler to be passed in, default to a fake one (util.format).
2728
var __ = opts.__ || function (str) {
2829
return util.format.apply(util, Array.prototype.slice.call(arguments))
2930
}
3031
var error = null
3132
var flags = {
32-
aliases: {},
33-
arrays: {},
34-
bools: {},
35-
strings: {},
36-
numbers: {},
37-
counts: {},
38-
normalize: {},
39-
configs: {},
40-
defaulted: {},
41-
nargs: {},
42-
coercions: {}
33+
aliases: Object.create(null),
34+
arrays: Object.create(null),
35+
bools: Object.create(null),
36+
strings: Object.create(null),
37+
numbers: Object.create(null),
38+
counts: Object.create(null),
39+
normalize: Object.create(null),
40+
configs: Object.create(null),
41+
defaulted: Object.create(null),
42+
nargs: Object.create(null),
43+
coercions: Object.create(null)
4344
}
4445
var negative = /^-[0-9]+(\.[0-9]+)?/
4546

@@ -67,11 +68,11 @@ function parse (args, opts) {
6768
flags.normalize[key] = true
6869
})
6970

70-
Object.keys(opts.narg || {}).forEach(function (k) {
71+
Object.keys(opts.narg || Object.create(null)).forEach(function (k) {
7172
flags.nargs[k] = opts.narg[k]
7273
})
7374

74-
Object.keys(opts.coerce || {}).forEach(function (k) {
75+
Object.keys(opts.coerce || Object.create(null)).forEach(function (k) {
7576
flags.coercions[k] = opts.coerce[k]
7677
})
7778

@@ -80,7 +81,7 @@ function parse (args, opts) {
8081
flags.configs[key] = true
8182
})
8283
} else {
83-
Object.keys(opts.config || {}).forEach(function (k) {
84+
Object.keys(opts.config || Object.create(null)).forEach(function (k) {
8485
flags.configs[k] = opts.config[k]
8586
})
8687
}
@@ -417,7 +418,7 @@ function parse (args, opts) {
417418
// set args from config.json file, this should be
418419
// applied last so that defaults can be applied.
419420
function setConfig (argv) {
420-
var configLookup = {}
421+
var configLookup = Object.create(null)
421422

422423
// expand defaults/aliases, in-case any happen to reference
423424
// the config.json file.
@@ -537,7 +538,7 @@ function parse (args, opts) {
537538
if (!configuration['dot-notation']) keys = [keys.join('.')]
538539

539540
keys.slice(0, -1).forEach(function (key) {
540-
o = (o[key] || {})
541+
o = (o[key] || Object.create(null))
541542
})
542543

543544
var key = keys[keys.length - 1]
@@ -551,8 +552,10 @@ function parse (args, opts) {
551552

552553
if (!configuration['dot-notation']) keys = [keys.join('.')]
553554

555+
keys = keys.map(sanitizeKey)
556+
554557
keys.slice(0, -1).forEach(function (key) {
555-
if (o[key] === undefined) o[key] = {}
558+
if (o[key] === undefined) o[key] = Object.create(null)
556559
o = o[key]
557560
})
558561

@@ -584,7 +587,7 @@ function parse (args, opts) {
584587
// extend the aliases list with inferred aliases.
585588
function extendAliases () {
586589
Array.prototype.slice.call(arguments).forEach(function (obj) {
587-
Object.keys(obj || {}).forEach(function (key) {
590+
Object.keys(obj || Object.create(null)).forEach(function (key) {
588591
// short-circuit if we've already added a key
589592
// to the aliases array, for example it might
590593
// exist in both 'opts.default' and 'opts.key'.
@@ -681,7 +684,7 @@ function parse (args, opts) {
681684
function combineAliases (aliases) {
682685
var aliasArrays = []
683686
var change = true
684-
var combined = {}
687+
var combined = Object.create(null)
685688

686689
// turn alias lookup hash {key: ['alias1', 'alias2']} into
687690
// a simple array ['key', 'alias1', 'alias2']
@@ -723,27 +726,18 @@ function combineAliases (aliases) {
723726
return combined
724727
}
725728

726-
function assign (defaults, configuration) {
727-
var o = {}
728-
configuration = configuration || {}
729-
730-
Object.keys(defaults).forEach(function (k) {
731-
o[k] = defaults[k]
732-
})
733-
Object.keys(configuration).forEach(function (k) {
734-
o[k] = configuration[k]
735-
})
736-
737-
return o
738-
}
739-
740729
// this function should only be called when a count is given as an arg
741730
// it is NOT called to set a default value
742731
// thus we can start the count at 1 instead of 0
743732
function increment (orig) {
744733
return orig !== undefined ? orig + 1 : 1
745734
}
746735

736+
function sanitizeKey (key) {
737+
if (key === '__proto__') return '___proto___'
738+
return key
739+
}
740+
747741
function Parser (args, opts) {
748742
var result = parse(args.slice(), opts)
749743

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"standard-version": "^4.0.0"
3535
},
3636
"dependencies": {
37-
"camelcase": "^3.0.0"
37+
"camelcase": "^3.0.0",
38+
"object.assign": "^4.1.0"
3839
},
3940
"files": [
4041
"lib",

test/fixtures/config.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,14 @@
33
"z": 55,
44
"foo": "baz",
55
"version": "1.0.2",
6-
"truthy": true
6+
"truthy": true,
7+
"toString": "method name",
8+
"__proto__": {
9+
"aaa": 99
10+
},
11+
"bar": {
12+
"__proto__": {
13+
"bbb": 100
14+
}
15+
}
716
}

test/yargs-parser.js

+28
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,26 @@ describe('yargs-parser', function () {
410410
describe('config', function () {
411411
var jsonPath = path.resolve(__dirname, './fixtures/config.json')
412412

413+
// Patching for https://snyk.io/vuln/SNYK-JS-YARGSPARSER-560381
414+
it('should not pollute the prototype', function () {
415+
const argv = parser(['--foo', 'bar'], {
416+
alias: {
417+
z: 'zoom'
418+
},
419+
default: {
420+
settings: jsonPath
421+
},
422+
config: 'settings'
423+
})
424+
425+
argv.should.have.property('herp', 'derp')
426+
argv.should.have.property('zoom', 55)
427+
argv.should.have.property('foo').and.deep.equal('bar')
428+
429+
expect({}.bbb).to.equal(undefined)
430+
expect({}.aaa).to.equal(undefined)
431+
})
432+
413433
// See: https://github.com/chevex/yargs/issues/12
414434
it('should load options and values from default config if specified', function () {
415435
var argv = parser([ '--foo', 'bar' ], {
@@ -2375,4 +2395,12 @@ describe('yargs-parser', function () {
23752395
})
23762396
argv.a.should.deep.equal(['a.txt', 'b.txt'])
23772397
})
2398+
2399+
// Patching for https://snyk.io/vuln/SNYK-JS-YARGSPARSER-560381
2400+
it('should not pollute the prototype', function () {
2401+
parser(['-f.__proto__.foo', '99', '-x.y.__proto__.bar', '100', '--__proto__', '200'])
2402+
Object.keys({}.__proto__).length.should.equal(0) // eslint-disable-line
2403+
expect({}.foo).to.equal(undefined)
2404+
expect({}.bar).to.equal(undefined)
2405+
})
23782406
})

0 commit comments

Comments
 (0)