CVE-2020-7706 (published 2020-08-18T10:15Z)

The package connie-lang before 0.1.1 are vulnerable to Prototype Pollution in the configuration language library used by connie.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-1321
cwe_name: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
description: The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
extended_description: 
				By adding or modifying attributes of an object prototype, it is possible to create attributes that exist on every object, or replace critical attributes with malicious ones. This can be problematic if the product depends on existence or non-existence of certain attributes, or uses pre-defined attributes of object prototype (such as hasOwnProperty, toString or valueOf).
				This weakness is usually exploited by using a special attribute of objects called proto,  constructor or prototype. Such attributes give access to the object prototype. This weakness is often found in code that assigns object attributes based on user input, or merges or clones objects recursively.
			
			

Fix information:
repo_url: https://github.com/mattinsler/connie-lang
commit_details: repo_url: https://github.com/mattinsler/connie-lang
author: Matt Insler
author_date: 2020-08-17 11:47:59-07:00
msg: added security tests, changed to yarn, bump version
repository: repo_url: https://github.com/mattinsler/connie-lang
repo_name: mattinsler/connie-lang
description: Configuration language for connie
repo_language: JavaScript
owner: mattinsler
file_changes: filename: connie-lang.js
change_type: ModificationType.MODIFY
diff: @@ -1,11 +1,11 @@
 var vm = require('vm');
 var isPlainObject = require('lodash.isplainobject');
 
-var getValue = function(obj, key) {
+var getValue = function (obj, key) {
   var o = obj;
   var keys = Array.isArray(key) ? key : key.split('.');
 
-  for (var x = 0; x < keys.length -1; ++x) {
+  for (var x = 0; x < keys.length - 1; ++x) {
     var k = keys[x];
     if (!o[k]) return;
     o = o[k];
@@ -14,18 +14,22 @@ var getValue = function(obj, key) {
   return o[keys[keys.length - 1]];
 };
 
-var setValue = function(obj, key, value) {
+var setValue = function (obj, key, value) {
   var o = obj;
   var keys = Array.isArray(key) ? key : key.split('.');
 
   for (var x = 1; x < keys.length; ++x) {
     var currentKey = keys[x];
     var lastKey = keys[x - 1];
-    if (typeof(currentKey) === 'number') {
-      if (!o[lastKey]) { o[lastKey] = []; }
+    if (typeof currentKey === 'number') {
+      if (!o[lastKey]) {
+        o[lastKey] = [];
+      }
       o = o[lastKey];
-    } else if (typeof(currentKey) === 'string') {
-      if (!o[lastKey]) { o[lastKey] = {}; }
+    } else if (typeof currentKey === 'string') {
+      if (!o[lastKey]) {
+        o[lastKey] = {};
+      }
       o = o[lastKey];
     } else {
       throw new Error('Oopsy, key arrays should only be strings and numbers:', keys);
@@ -36,54 +40,66 @@ var setValue = function(obj, key, value) {
   return obj;
 };
 
-
 var PARSE_RX = /([^\}:]+)(:([^\}]+))?/;
 
 var EnvVarInterpreter = {
   type: '$',
-  replace: function(value, context, parseContext) {
+  replace: function (value, context, parseContext) {
     var m = PARSE_RX.exec(parseContext.value);
-    if (!m) { return value; }
+    if (!m) {
+      return value;
+    }
 
     var newValue = context.env[m[1]] || m[3] || '';
     return value.slice(0, parseContext.start) + newValue + value.slice(parseContext.end);
-  }
+  },
 };
 
 var ReferenceInterpreter = {
   type: '@',
-  replace: function(value, context, parseContext) {
+  replace: function (value, context, parseContext) {
     var m = PARSE_RX.exec(parseContext.value);
-    if (!m) { return value; }
+    if (!m) {
+      return value;
+    }
 
     var newValue = getValue(context.config, m[1]) || m[3];
-    if (value === parseContext.match) { return newValue; }
+    if (value === parseContext.match) {
+      return newValue;
+    }
     return value.slice(0, parseContext.start) + newValue + value.slice(parseContext.end);
-  }
+  },
 };
 
 var Interpreters = [ReferenceInterpreter, EnvVarInterpreter];
 
 var ConnieLang = {
   Interpreters: Interpreters,
-  InterpretersByType: Interpreters.reduce(function(o, i) {o[i.type] = i; return o;}, {}),
+  InterpretersByType: Interpreters.reduce(function (o, i) {
+    o[i.type] = i;
+    return o;
+  }, {}),
 
-  getEntries: function(config) {
+  getEntries: function (config) {
     var entries = [];
 
-    var iter = function(value, prefix) {
+    var iter = function (value, prefix) {
       if (!prefix) prefix = [];
 
       if (Array.isArray(value)) {
-        value.forEach(function(arrValue, idx) {
+        value.forEach(function (arrValue, idx) {
           iter(arrValue, prefix.concat(idx));
         });
       } else if (isPlainObject(value)) {
-        Object.keys(value).forEach(function(key) {
+        var keys = Object.keys(value);
+        if (keys.includes('__proto__') || keys.includes('constructor')) {
+          return;
+        }
+        keys.forEach(function (key) {
           iter(value[key], prefix.concat(key));
         });
       } else {
-        entries.push({key: prefix, value: value});
+        entries.push({ key: prefix, value: value });
       }
     };
 
@@ -91,8 +107,10 @@ var ConnieLang = {
     return entries;
   },
 
-  firstInnermostInterpreterFromValue: function(value) {
-    if (value === null || value === undefined) { return null; }
+  firstInnermostInterpreterFromValue: function (value) {
+    if (value === null || value === undefined) {
+      return null;
+    }
 
     var start = -1;
     var interpreterTypes = Object.keys(ConnieLang.InterpretersByType);
@@ -109,9 +127,9 @@ var ConnieLang = {
           value: value.slice(start + 2, idx),
           start: start,
           end: idx + 1,
-          replaceInValue: function(value, context) {
+          replaceInValue: function (value, context) {
             return interpreter.replace(value, context, parseContext);
-          }
+          },
         };
 
         return parseContext;
@@ -121,19 +139,19 @@ var ConnieLang = {
     return null;
   },
 
-  parse: function(configObj, envObj) {
+  parse: function (configObj, envObj) {
     var context = {
-      config: configObj,
-      env: envObj || process.env
+      config: Object.assign(Object.create(null), configObj),
+      env: envObj || process.env,
     };
 
     var entries = ConnieLang.getEntries(context.config);
 
     // iterate until no updates have been made
-    var digest = function() {
+    var digest = function () {
       var updated = false;
 
-      entries.forEach(function(e) {
+      entries.forEach(function (e) {
         var interpreter = ConnieLang.firstInnermostInterpreterFromValue(e.value, context);
         if (!interpreter) return;
 
@@ -147,15 +165,15 @@ var ConnieLang = {
       return updated;
     };
 
-    while(digest()) ;
+    while (digest());
 
     var result = {};
-    entries.forEach(function(e) {
+    entries.forEach(function (e) {
       setValue(result, e.key, e.value);
     });
 
     return result;
-  }
+  },
 };
 
 module.exports = ConnieLang;

programming_language: JavaScript
filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,7 @@
 {
   "name": "connie-lang",
   "description": "Configuration language for connie",
-  "version": "0.1.0",
+  "version": "0.1.1",
   "homepage": "https://github.com/mattinsler/connie-lang",
   "repository": {
     "type": "git",
@@ -23,5 +23,8 @@
   "devDependencies": {
     "mocha": "2.0.1"
   },
-  "keywords": ["configuration", "connie"]
-}
+  "keywords": [
+    "configuration",
+    "connie"
+  ]
+}
\ No newline at end of file

programming_language: JavaScript
filename: connie-lang.security.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,20 @@
+var assert = require('assert');
+var ConnieLang = require('../');
+
+describe('ConnieLang', function () {
+  it('should not allow prototype pollution at the top level', function () {
+    var obj = Object.create(null);
+    obj['__proto__'] = { polluted: true };
+    var config = ConnieLang.parse(obj);
+    assert.deepEqual(config, {});
+    assert.equal(Object.polluted, undefined);
+  });
+
+  it('should not allow prototype pollution at a nested level', function () {
+    var obj = Object.create(null);
+    obj['__proto__'] = { polluted: true };
+    var config = ConnieLang.parse({ foo: obj });
+    assert.deepEqual(config, {});
+    assert.equal(Object.polluted, undefined);
+  });
+});

programming_language: JavaScript
filename: connie-lang.test.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,193 @@
+var assert = require('assert');
+var ConnieLang = require('../');
+
+describe('ConnieLang', function () {
+  describe('getEntries', function () {
+    it('should return entries for nested objects', function () {
+      var entries = ConnieLang.getEntries({
+        foo: 'bar',
+        bar: {
+          baz: {
+            a: {
+              b: 'c',
+              c: 'd',
+              d: ['e', 'f', 'g'],
+            },
+          },
+        },
+      });
+
+      assert.deepEqual(entries, [
+        { key: ['foo'], value: 'bar' },
+        { key: ['bar', 'baz', 'a', 'b'], value: 'c' },
+        { key: ['bar', 'baz', 'a', 'c'], value: 'd' },
+        { key: ['bar', 'baz', 'a', 'd', 0], value: 'e' },
+        { key: ['bar', 'baz', 'a', 'd', 1], value: 'f' },
+        { key: ['bar', 'baz', 'a', 'd', 2], value: 'g' },
+      ]);
+    });
+
+    it('should return entries for an array', function () {
+      var entries = ConnieLang.getEntries(['foo', { bar: 'baz' }]);
+
+      assert.deepEqual(entries, [
+        { key: [0], value: 'foo' },
+        { key: [1, 'bar'], value: 'baz' },
+      ]);
+    });
+
+    it('filters out entries that have __proto__ in the key path', function () {
+      var obj = Object.create(null);
+      obj['__proto__'] = { polluted: true };
+      var entries = ConnieLang.getEntries(obj);
+      assert.deepEqual(entries, []);
+    });
+
+    it('filters out entries that have __proto__ in the deep key path', function () {
+      var obj = Object.create(null);
+      obj['__proto__'] = { polluted: true };
+      var entries = ConnieLang.getEntries({ foo: obj });
+      assert.deepEqual(entries, []);
+    });
+
+    it('filters out entries that have constructor in the key path', function () {
+      var entries = ConnieLang.getEntries({ constructor: { polluted: true } });
+      assert.deepEqual(entries, []);
+    });
+
+    it('filters out entries that have constructor in the deep key path', function () {
+      var entries = ConnieLang.getEntries({ foo: { constructor: { polluted: true } } });
+      assert.deepEqual(entries, []);
+    });
+  });
+
+  describe('firstInnermostInterpreterFromValue', function () {
+    it('should return null when no interpreters are found', function () {
+      var interpreter = ConnieLang.firstInnermostInterpreterFromValue('foobar');
+      assert.equal(interpreter, null);
+    });
+
+    it('should handle complex values', function () {
+      var value = '${FOO_${PORT}} @{foo.bar}';
+
+      var interpreter = ConnieLang.firstInnermostInterpreterFromValue(value);
+      assert.equal(interpreter.type, '$');
+      assert.equal(interpreter.match, '${PORT}');
+      assert.equal(interpreter.value, 'PORT');
+      assert.equal(interpreter.start, 6);
+      assert.equal(interpreter.end, 13);
+    });
+  });
+
+  describe('parse', function () {
+    it('should parse environment variables correctly', function () {
+      var config = ConnieLang.parse(
+        {
+          a: 'PORT',
+          b: {
+            c: {
+              d: 'e',
+              e: '${@{a}}',
+            },
+          },
+        },
+        {
+          PORT: '3000',
+        }
+      );
+
+      assert.deepEqual(config, {
+        a: 'PORT',
+        b: {
+          c: {
+            d: 'e',
+            e: '3000',
+          },
+        },
+      });
+    });
+
+    it('should parse inside of arrays', function () {
+      var config = ConnieLang.parse(
+        {
+          bar: 'hey',
+          arr: [{ foo: '@{bar}' }, { bar: '${FOOBAR:4}' }, '${PORT}'],
+        },
+        {
+          PORT: '3000',
+        }
+      );
+
+      assert.deepEqual(config, {
+        bar: 'hey',
+        arr: [{ foo: 'hey' }, { bar: 4 }, '3000'],
+      });
+    });
+
+    it('should parse env inside of env', function () {
+      var config = ConnieLang.parse(
+        {
+          a: '${FOO_${BAR}}',
+          b: '${FOO_${BAZ}}',
+        },
+        {
+          BAR: 'BAR',
+          BAZ: 'BAZ',
+          FOO_BAR: 'hello',
+          FOO_BAZ: 'world',
+        }
+      );
+
+      assert.deepEqual(config, {
+        a: 'hello',
+        b: 'world',
+      });
+    });
+
+    it('should ignore null and undefined values', function () {
+      var config = ConnieLang.parse({
+        a: {
+          b: ['foo', null, 4],
+        },
+        b: undefined,
+      });
+
+      assert.deepEqual(config, {
+        a: {
+          b: ['foo', null, 4],
+        },
+        b: undefined,
+      });
+    });
+
+    it('should substitute a missing env var with an empty string', function () {
+      var config = ConnieLang.parse({
+        foo: '${HELLO}',
+      });
+
+      assert.deepEqual(config, {
+        foo: '',
+      });
+    });
+
+    it('should substitute a default value when env var does not exist', function () {
+      var config = ConnieLang.parse({
+        foo: '${HELLO:default}',
+      });
+
+      assert.deepEqual(config, {
+        foo: 'default',
+      });
+    });
+
+    it('should substitute a default value when ref does not exist', function () {
+      var config = ConnieLang.parse({
+        foo: '@{bar:default}',
+      });
+
+      assert.deepEqual(config, {
+        foo: 'default',
+      });
+    });
+  });
+});

programming_language: JavaScript
filename: test-connie-lang.js
change_type: ModificationType.DELETE
diff: @@ -1,179 +0,0 @@
-var assert = require('assert');
-var ConnieLang = require('../');
-
-describe('ConnieLang', function() {
-  describe('getEntries', function() {
-    it('should return entries for nested objects', function() {
-      var entries = ConnieLang.getEntries({
-        foo: 'bar',
-        bar: {
-          baz: {
-            a: {
-              b: 'c',
-              c: 'd',
-              d: ['e', 'f', 'g']
-            }
-          }
-        }
-      });
-
-      assert.deepEqual(entries, [
-        {key: ['foo'], value: 'bar'},
-        {key: ['bar', 'baz', 'a', 'b'], value: 'c'},
-        {key: ['bar', 'baz', 'a', 'c'], value: 'd'},
-        {key: ['bar', 'baz', 'a', 'd', 0], value: 'e'},
-        {key: ['bar', 'baz', 'a', 'd', 1], value: 'f'},
-        {key: ['bar', 'baz', 'a', 'd', 2], value: 'g'}
-      ]);
-    });
-
-    it('should return entries for an array', function() {
-      var entries = ConnieLang.getEntries([
-        'foo',
-        {bar: 'baz'}
-      ]);
-
-      assert.deepEqual(entries, [
-        {key: [0], value: 'foo'},
-        {key: [1, 'bar'], value: 'baz'}
-      ]);
-    });
-  });
-
-  describe('firstInnermostInterpreterFromValue', function() {
-    it('should return null when no interpreters are found', function() {
-      var interpreter = ConnieLang.firstInnermostInterpreterFromValue('foobar');
-      assert.equal(interpreter, null);
-    });
-
-    it('should handle complex values', function() {
-      var value = '${FOO_${PORT}} @{foo.bar}';
-
-      var interpreter = ConnieLang.firstInnermostInterpreterFromValue(value);
-      assert.equal(interpreter.type, '$');
-      assert.equal(interpreter.match, '${PORT}');
-      assert.equal(interpreter.value, 'PORT');
-      assert.equal(interpreter.start, 6);
-      assert.equal(interpreter.end, 13);
-    });
-  });
-
-  describe('parse', function() {
-    it('should parse environment variables correctly', function() {
-      var config = ConnieLang.parse({
-        a: 'PORT',
-        b: {
-          c: {
-            d: 'e',
-            e: '${@{a}}'
-          }
-        },
-      }, {
-        PORT: '3000'
-      });
-
-      assert.deepEqual(config, {
-        a: 'PORT',
-        b: {
-          c: {
-            d: 'e',
-            e: '3000'
-          }
-        }
-      });
-    });
-
-    it('should parse inside of arrays', function() {
-      var config = ConnieLang.parse({
-        bar: 'hey',
-        arr: [
-          {foo: '@{bar}'},
-          {bar: '${FOOBAR:4}'},
-          '${PORT}'
-        ]
-      }, {
-        PORT: '3000'
-      });
-
-      assert.deepEqual(config, {
-        bar: 'hey',
-        arr: [
-          {foo: 'hey'},
-          {bar: 4},
-          '3000'
-        ]
-      });
-    });
-
-    it('should parse env inside of env', function() {
-      var config = ConnieLang.parse({
-        a: '${FOO_${BAR}}',
-        b: '${FOO_${BAZ}}'
-      }, {
-        BAR: 'BAR',
-        BAZ: 'BAZ',
-        FOO_BAR: 'hello',
-        FOO_BAZ: 'world'
-      });
-
-      assert.deepEqual(config, {
-        a: 'hello',
-        b: 'world'
-      });
-    });
-
-    it('should ignore null and undefined values', function() {
-      var config = ConnieLang.parse({
-        a: {
-          b: [
-            'foo',
-            null,
-            4
-          ]
-        },
-        b: undefined
-      });
-
-      assert.deepEqual(config, {
-        a: {
-          b: [
-            'foo',
-            null,
-            4
-          ]
-        },
-        b: undefined
-      });
-    });
-
-    it('should substitute a missing env var with an empty string', function() {
-      var config = ConnieLang.parse({
-        foo: '${HELLO}'
-      });
-
-      assert.deepEqual(config, {
-        foo: ''
-      });
-    });
-
-    it('should substitute a default value when env var does not exist', function() {
-      var config = ConnieLang.parse({
-        foo: '${HELLO:default}'
-      });
-
-      assert.deepEqual(config, {
-        foo: 'default'
-      });
-    });
-
-    it('should substitute a default value when ref does not exist', function() {
-      var config = ConnieLang.parse({
-        foo: '@{bar:default}',
-      });
-
-      assert.deepEqual(config, {
-        foo: 'default'
-      });
-    });
-  });
-});

programming_language: unknown
filename: yarn.lock
change_type: ModificationType.ADD
diff: @@ -0,0 +1,249 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+commander@0.6.1:
+  version "0.6.1"
+  resolved "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06"
+  integrity sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=
+
+commander@2.3.0:
+  version "2.3.0"
+  resolved "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
+  integrity sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=
+
+debug@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmjs.org/debug/-/debug-2.0.0.tgz#89bd9df6732b51256bc6705342bba02ed12131ef"
+  integrity sha1-ib2d9nMrUSVrxnBTQrugLtEhMe8=
+  dependencies:
+    ms "0.6.2"
+
+diff@1.0.8:
+  version "1.0.8"
+  resolved "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz#343276308ec991b7bc82267ed55bc1411f971666"
+  integrity sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=
+
+escape-string-regexp@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1"
+  integrity sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=
+
+glob@3.2.3:
+  version "3.2.3"
+  resolved "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz#e313eeb249c7affaa5c475286b0e115b59839467"
+  integrity sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=
+  dependencies:
+    graceful-fs "~2.0.0"
+    inherits "2"
+    minimatch "~0.2.11"
+
+graceful-fs@~2.0.0:
+  version "2.0.3"
+  resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz#7cd2cdb228a4a3f36e95efa6cc142de7d1a136d0"
+  integrity sha1-fNLNsiiko/Nule+mzBQt59GhNtA=
+
+growl@1.8.1:
+  version "1.8.1"
+  resolved "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz#4b2dec8d907e93db336624dcec0183502f8c9428"
+  integrity sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg=
+
+inherits@2:
+  version "2.0.4"
+  resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+jade@0.26.3:
+  version "0.26.3"
+  resolved "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c"
+  integrity sha1-jxDXl32NefL2/4YqgbBRPMslaGw=
+  dependencies:
+    commander "0.6.1"
+    mkdirp "0.3.0"
+
+lodash._basebind@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.4.1.tgz#e940b9ebdd27c327e0a8dab1b55916c5341e9575"
+  integrity sha1-6UC5690nwyfgqNqxtVkWxTQelXU=
+  dependencies:
+    lodash._basecreate "~2.4.1"
+    lodash._setbinddata "~2.4.1"
+    lodash._slice "~2.4.1"
+    lodash.isobject "~2.4.1"
+
+lodash._basecreate@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.4.1.tgz#f8e6f5b578a9e34e541179b56b8eeebf4a287e08"
+  integrity sha1-+Ob1tXip405UEXm1a47uv0oofgg=
+  dependencies:
+    lodash._isnative "~2.4.1"
+    lodash.isobject "~2.4.1"
+    lodash.noop "~2.4.1"
+
+lodash._basecreatecallback@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.4.1.tgz#7d0b267649cb29e7a139d0103b7c11fae84e4851"
+  integrity sha1-fQsmdknLKeehOdAQO3wR+uhOSFE=
+  dependencies:
+    lodash._setbinddata "~2.4.1"
+    lodash.bind "~2.4.1"
+    lodash.identity "~2.4.1"
+    lodash.support "~2.4.1"
+
+lodash._basecreatewrapper@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.4.1.tgz#4d31f2e7de7e134fbf2803762b8150b32519666f"
+  integrity sha1-TTHy595+E0+/KAN2K4FQsyUZZm8=
+  dependencies:
+    lodash._basecreate "~2.4.1"
+    lodash._setbinddata "~2.4.1"
+    lodash._slice "~2.4.1"
+    lodash.isobject "~2.4.1"
+
+lodash._createwrapper@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.4.1.tgz#51d6957973da4ed556e37290d8c1a18c53de1607"
+  integrity sha1-UdaVeXPaTtVW43KQ2MGhjFPeFgc=
+  dependencies:
+    lodash._basebind "~2.4.1"
+    lodash._basecreatewrapper "~2.4.1"
+    lodash._slice "~2.4.1"
+    lodash.isfunction "~2.4.1"
+
+lodash._isnative@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz#3ea6404b784a7be836c7b57580e1cdf79b14832c"
+  integrity sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=
+
+lodash._objecttypes@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz#7c0b7f69d98a1f76529f890b0cdb1b4dfec11c11"
+  integrity sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=
+
+lodash._setbinddata@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.4.1.tgz#f7c200cd1b92ef236b399eecf73c648d17aa94d2"
+  integrity sha1-98IAzRuS7yNrOZ7s9zxkjReqlNI=
+  dependencies:
+    lodash._isnative "~2.4.1"
+    lodash.noop "~2.4.1"
+
+lodash._shimisplainobject@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._shimisplainobject/-/lodash._shimisplainobject-2.4.1.tgz#01ec93b2ee63e59f1aa83899ac6fa0905ac7596f"
+  integrity sha1-AeyTsu5j5Z8aqDiZrG+gkFrHWW8=
+  dependencies:
+    lodash.forin "~2.4.1"
+    lodash.isfunction "~2.4.1"
+
+lodash._slice@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.4.1.tgz#745cf41a53597b18f688898544405efa2b06d90f"
+  integrity sha1-dFz0GlNZexj2iImFREBe+isG2Q8=
+
+lodash.bind@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.4.1.tgz#5d19fa005c8c4d236faf4742c7b7a1fcabe29267"
+  integrity sha1-XRn6AFyMTSNvr0dCx7eh/Kvikmc=
+  dependencies:
+    lodash._createwrapper "~2.4.1"
+    lodash._slice "~2.4.1"
+
+lodash.forin@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash.forin/-/lodash.forin-2.4.1.tgz#8089eaed7d25b08672b7c66fd07ac55d062320eb"
+  integrity sha1-gInq7X0lsIZyt8Zv0HrFXQYjIOs=
+  dependencies:
+    lodash._basecreatecallback "~2.4.1"
+    lodash._objecttypes "~2.4.1"
+
+lodash.identity@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.4.1.tgz#6694cffa65fef931f7c31ce86c74597cf560f4f1"
+  integrity sha1-ZpTP+mX++TH3wxzobHRZfPVg9PE=
+
+lodash.isfunction@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.4.1.tgz#2cfd575c73e498ab57e319b77fa02adef13a94d1"
+  integrity sha1-LP1XXHPkmKtX4xm3f6Aq3vE6lNE=
+
+lodash.isobject@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz#5a2e47fe69953f1ee631a7eba1fe64d2d06558f5"
+  integrity sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=
+  dependencies:
+    lodash._objecttypes "~2.4.1"
+
+lodash.isplainobject@2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-2.4.1.tgz#ac7385e2ea9ac0321f30dc3b8032a6d2231a8011"
+  integrity sha1-rHOF4uqawDIfMNw7gDKm0iMagBE=
+  dependencies:
+    lodash._isnative "~2.4.1"
+    lodash._shimisplainobject "~2.4.1"
+
+lodash.noop@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.4.1.tgz#4fb54f816652e5ae10e8f72f717a388c7326538a"
+  integrity sha1-T7VPgWZS5a4Q6PcvcXo4jHMmU4o=
+
+lodash.support@~2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmjs.org/lodash.support/-/lodash.support-2.4.1.tgz#320e0b67031673c28d7a2bb5d9e0331a45240515"
+  integrity sha1-Mg4LZwMWc8KNeiu12eAzGkUkBRU=
+  dependencies:
+    lodash._isnative "~2.4.1"
+
+lru-cache@2:
+  version "2.7.3"
+  resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
+  integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=
+
+minimatch@~0.2.11:
+  version "0.2.14"
+  resolved "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a"
+  integrity sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=
+  dependencies:
+    lru-cache "2"
+    sigmund "~1.0.0"
+
+minimist@0.0.8:
+  version "0.0.8"
+  resolved "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+  integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
+
+mkdirp@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
+  integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=
+
+mkdirp@0.5.0:
+  version "0.5.0"
+  resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12"
+  integrity sha1-HXMHam35hs2TROFecfzAWkyavxI=
+  dependencies:
+    minimist "0.0.8"
+
+mocha@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.npmjs.org/mocha/-/mocha-2.0.1.tgz#5a16e88b856d0c4145d8c6888c27ebd4fab13e90"
+  integrity sha1-Whboi4VtDEFF2MaIjCfr1PqxPpA=
+  dependencies:
+    commander "2.3.0"
+    debug "2.0.0"
+    diff "1.0.8"
+    escape-string-regexp "1.0.2"
+    glob "3.2.3"
+    growl "1.8.1"
+    jade "0.26.3"
+    mkdirp "0.5.0"
+
+ms@0.6.2:
+  version "0.6.2"
+  resolved "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz#d89c2124c6fdc1353d65a8b77bf1aac4b193708c"
+  integrity sha1-2JwhJMb9wTU9Zai3e/GqxLGTcIw=
+
+sigmund@~1.0.0:
+  version "1.0.1"
+  resolved "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
+  integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=

programming_language: Shell