Repository: yargs/yargs-parser
Pull request #258 (2020-03-16 07:05:48)

fix: __proto__ will now be replaced with ___proto___ in parse
addresses potential for prototype pollution.


Patch:
diff --git a/index.js b/index.js
index 5c6547d..b7a1839 100644
--- a/index.js
+++ b/index.js
@@ -697,6 +697,10 @@ function parse (args, opts) {
     if (!configuration['dot-notation']) keys = [keys.join('.')]
 
     keys.slice(0, -1).forEach(function (key, index) {
+      // TODO(bcoe): in the next major version of yargs, switch to
+      // Object.create(null) for dot notation:
+      key = sanitizeKey(key)
+
       if (typeof o === 'object' && o[key] === undefined) {
         o[key] = {}
       }
@@ -716,7 +720,10 @@ function parse (args, opts) {
       }
     })
 
-    const key = keys[keys.length - 1]
+    // TODO(bcoe): in the next major version of yargs, switch to
+    // Object.create(null) for dot notation:
+    const key = sanitizeKey(keys[keys.length - 1])
+
     const isTypeArray = checkAllAliases(keys.join('.'), flags.arrays)
     const isValueArray = Array.isArray(value)
     let duplicate = configuration['duplicate-arguments-array']
@@ -1001,4 +1008,11 @@ Parser.detailed = function (args, opts) {
   return parse(args.slice(), opts)
 }
 
+// TODO(bcoe): in the next major version of yargs, switch to
+// Object.create(null) for dot notation:
+function sanitizeKey (key) {
+  if (key === '__proto__') return '___proto___'
+  return key
+}
+
 module.exports = Parser


Test patch:
diff --git a/test/fixtures/config.json b/test/fixtures/config.json
index 1f571f2..7065ae6 100644
--- a/test/fixtures/config.json
+++ b/test/fixtures/config.json
@@ -4,5 +4,13 @@
     "foo": "baz",
     "version": "1.0.2",
     "truthy": true,
-    "toString": "method name"
+    "toString": "method name",
+    "__proto__": {
+      "aaa": 99
+    },
+    "bar": {
+      "__proto__": {
+        "bbb": 100
+      }
+    }
 }
diff --git a/test/yargs-parser.js b/test/yargs-parser.js
index 3e13899..217f188 100644
--- a/test/yargs-parser.js
+++ b/test/yargs-parser.js
@@ -727,6 +727,25 @@ describe('yargs-parser', function () {
 
       argv.error.message.should.equal('someone set us up the bomb')
     })
+
+    it('should not pollute the prototype', function () {
+      const argv = parser(['--foo', 'bar'], {
+        alias: {
+          z: 'zoom'
+        },
+        default: {
+          settings: jsonPath
+        },
+        config: 'settings'
+      })
+
+      argv.should.have.property('herp', 'derp')
+      argv.should.have.property('zoom', 55)
+      argv.should.have.property('foo').and.deep.equal('bar')
+
+      expect({}.bbb).to.equal(undefined)
+      expect({}.aaa).to.equal(undefined)
+    })
   })
 
   describe('config objects', function () {
@@ -974,6 +993,13 @@ describe('yargs-parser', function () {
       argv.f.foo.should.eql(99)
       argv.f.bar.should.eql(true)
     })
+
+    it('should not pollute the prototype', function () {
+      parser(['-f.__proto__.foo', '99', '-x.y.__proto__.bar', '100', '--__proto__', '200'])
+      Object.keys({}.__proto__).length.should.equal(0) // eslint-disable-line
+      expect({}.foo).to.equal(undefined)
+      expect({}.bar).to.equal(undefined)
+    })
   })
 
   it('should set boolean and alias using explicit true', function () {
@@ -3702,4 +3728,24 @@ describe('yargs-parser', function () {
       argv._.should.eql([101, 102])
     })
   })
+
+  it('should replace the key __proto__ with the key ___proto___', function () {
+    const argv = parser(['-f.__proto__.foo', '99', '-x.y.__proto__.bar', '100', '--__proto__', '200'])
+    argv.should.eql({
+      _: [],
+      ___proto___: 200,
+      f: {
+        ___proto___: {
+          foo: 99
+        }
+      },
+      x: {
+        y: {
+          ___proto___: {
+            bar: 100
+          }
+        }
+      }
+    })
+  })
 })