CVE-2020-7706: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: The package connie-lang before 0.1.1 are vulnerable to Prototype Pollution in the configuration language library used by connie.
CWE 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.
CVSS v3 base score: 9.8
Published: 2020-08-18T10:15Z
Repository: https://github.com/mattinsler/connie-lang
Language: JavaScript
Files changed: .gitignore, .travis.yml, README.md, lib/connie-lang.js, package.json, test/connie-lang.security.test.js, test/connie-lang.test.js, yarn.lock
Commit date: 2020-08-17 11:47:59 -0700
Commit message: added security tests, changed to yarn, bump version

Diff: diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6e5919d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - "0.10"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5299448
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+# connie-lang
+
+Configuration language for [connie](https://github.com/mattinsler/connie).
+
+## Installation
+
+```bash
+$ npm install --save connie-lang
+```
+
+## Usage
+
+```javascript
+var ConnieLang = require('connie-lang');
+
+var config = ConnieLang.parse({
+  common: {
+    domain: 'mattinsler.com'
+  },
+  endpoints: {
+    api: 'api.@{common.domain}'
+  },
+  server: {
+    port: '${PORT:3000}'
+  }
+}, process.env);
+
+```
+
+## Language Reference
+
+**${ENV_VAR}** Environment Variable Replacement
+
+**@{ref}** Self-Reference Replacement
+
+### Default Values
+
+You can provide a default value in either of the patterns above, using `:` as
+a separator.
+
+For instance, `${PORT:3000}` means to substitute the PORT
+environment variable, but if that does not exist, it will return the string
+`'3000'` instead. This also works with reference patterns, like
+`@{common.domain:foo.com}`, which will use `'foo.com'` as the default value
+if `common.domain` does not exist in the current config.
+
+## Examples
+
+##### original config object
+```json
+{
+  "common": {
+    "domain": "mattinsler.com"
+  },
+  "endpoints": {
+    "api": "api.@{common.domain}"
+  },
+  "server": {
+    "port": "${PORT:3000}"
+  }
+}
+```
+
+When this configuration is executed with an empty environment, the result is:
+
+##### parsed config object
+```json
+{
+  "common": {
+    "domain": "mattinsler.com"
+  },
+  "endpoints": {
+    "api": "api.mattinsler.com"
+  },
+  "server": {
+    "port": "3000"
+  }
+}
+```
+
+However, if the PORT environment variable is set to 4000, then this would change to:
+
+##### parsed config object
+```json
+{
+  "common": {
+    "domain": "mattinsler.com"
+  },
+  "endpoints": {
+    "api": "api.mattinsler.com"
+  },
+  "server": {
+    "port": "4000"
+  }
+}
+```
diff --git a/lib/connie-lang.js b/lib/connie-lang.js
new file mode 100644
index 0000000..bab66e3
--- /dev/null
+++ b/lib/connie-lang.js
@@ -0,0 +1,179 @@
+var vm = require('vm');
+var isPlainObject = require('lodash.isplainobject');
+
+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) {
+    var k = keys[x];
+    if (!o[k]) return;
+    o = o[k];
+  }
+
+  return o[keys[keys.length - 1]];
+};
+
+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] = [];
+      }
+      o = 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);
+    }
+  }
+
+  o[keys[keys.length - 1]] = value;
+  return obj;
+};
+
+var PARSE_RX = /([^\}:]+)(:([^\}]+))?/;
+
+var EnvVarInterpreter = {
+  type: '$',
+  replace: function (value, context, parseContext) {
+    var m = PARSE_RX.exec(parseContext.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) {
+    var m = PARSE_RX.exec(parseContext.value);
+    if (!m) {
+      return value;
+    }
+
+    var newValue = getValue(context.config, m[1]) || m[3];
+    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;
+  }, {}),
+
+  getEntries: function (config) {
+    var entries = [];
+
+    var iter = function (value, prefix) {
+      if (!prefix) prefix = [];
+
+      if (Array.isArray(value)) {
+        value.forEach(function (arrValue, idx) {
+          iter(arrValue, prefix.concat(idx));
+        });
+      } else if (isPlainObject(value)) {
+        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 });
+      }
+    };
+
+    iter(config);
+    return entries;
+  },
+
+  firstInnermostInterpreterFromValue: function (value) {
+    if (value === null || value === undefined) {
+      return null;
+    }
+
+    var start = -1;
+    var interpreterTypes = Object.keys(ConnieLang.InterpretersByType);
+
+    for (var idx = 1; idx < value.length; ++idx) {
+      if (value[idx] === '{' && interpreterTypes.indexOf(value[idx - 1]) !== -1) {
+        start = idx - 1;
+      } else if (value[idx] === '}' && start !== -1) {
+        var interpreter = ConnieLang.InterpretersByType[value[start]];
+
+        var parseContext = {
+          type: interpreter.type,
+          match: value.slice(start, idx + 1),
+          value: value.slice(start + 2, idx),
+          start: start,
+          end: idx + 1,
+          replaceInValue: function (value, context) {
+            return interpreter.replace(value, context, parseContext);
+          },
+        };
+
+        return parseContext;
+      }
+    }
+
+    return null;
+  },
+
+  parse: function (configObj, envObj) {
+    var context = {
+      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 updated = false;
+
+      entries.forEach(function (e) {
+        var interpreter = ConnieLang.firstInnermostInterpreterFromValue(e.value, context);
+        if (!interpreter) return;
+
+        var newValue = interpreter.replaceInValue(e.value, context);
+        if (newValue !== e.value) {
+          e.value = newValue;
+          updated = true;
+        }
+      });
+
+      return updated;
+    };
+
+    while (digest());
+
+    var result = {};
+    entries.forEach(function (e) {
+      setValue(result, e.key, e.value);
+    });
+
+    return result;
+  },
+};
+
+module.exports = ConnieLang;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8da3ecc
--- /dev/null
+++ b/package.json
@@ -0,0 +1,30 @@
+{
+  "name": "connie-lang",
+  "description": "Configuration language for connie",
+  "version": "0.1.1",
+  "homepage": "https://github.com/mattinsler/connie-lang",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/mattinsler/connie-lang.git"
+  },
+  "bugs": {
+    "url": "https://github.com/mattinsler/connie-lang/issues"
+  },
+  "main": "lib/connie-lang",
+  "scripts": {
+    "test": "mocha"
+  },
+  "engines": {
+    "node": ">= 0.10.0"
+  },
+  "dependencies": {
+    "lodash.isplainobject": "2.4.1"
+  },
+  "devDependencies": {
+    "mocha": "2.0.1"
+  },
+  "keywords": [
+    "configuration",
+    "connie"
+  ]
+}
\ No newline at end of file
diff --git a/test/connie-lang.security.test.js b/test/connie-lang.security.test.js
new file mode 100644
index 0000000..bfd8511
--- /dev/null
+++ b/test/connie-lang.security.test.js
@@ -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);
+  });
+});
diff --git a/test/connie-lang.test.js b/test/connie-lang.test.js
new file mode 100644
index 0000000..516b057
--- /dev/null
+++ b/test/connie-lang.test.js
@@ -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',
+      });
+    });
+  });
+});
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..061a930
--- /dev/null
+++ b/yarn.lock
@@ -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=

Fixed code: node_modules
language: node_js
node_js:
  - "0.10"
# connie-lang

Configuration language for [connie](https://github.com/mattinsler/connie).

## Installation

```bash
$ npm install --save connie-lang
```

## Usage

```javascript
var ConnieLang = require('connie-lang');

var config = ConnieLang.parse({
  common: {
    domain: 'mattinsler.com'
  },
  endpoints: {
    api: 'api.@{common.domain}'
  },
  server: {
    port: '${PORT:3000}'
  }
}, process.env);

```

## Language Reference

**${ENV_VAR}** Environment Variable Replacement

**@{ref}** Self-Reference Replacement

### Default Values

You can provide a default value in either of the patterns above, using `:` as
a separator.

For instance, `${PORT:3000}` means to substitute the PORT
environment variable, but if that does not exist, it will return the string
`'3000'` instead. This also works with reference patterns, like
`@{common.domain:foo.com}`, which will use `'foo.com'` as the default value
if `common.domain` does not exist in the current config.

## Examples

##### original config object
```json
{
  "common": {
    "domain": "mattinsler.com"
  },
  "endpoints": {
    "api": "api.@{common.domain}"
  },
  "server": {
    "port": "${PORT:3000}"
  }
}
```

When this configuration is executed with an empty environment, the result is:

##### parsed config object
```json
{
  "common": {
    "domain": "mattinsler.com"
  },
  "endpoints": {
    "api": "api.mattinsler.com"
  },
  "server": {
    "port": "3000"
  }
}
```

However, if the PORT environment variable is set to 4000, then this would change to:

##### parsed config object
```json
{
  "common": {
    "domain": "mattinsler.com"
  },
  "endpoints": {
    "api": "api.mattinsler.com"
  },
  "server": {
    "port": "4000"
  }
}
```
var vm = require('vm');
var isPlainObject = require('lodash.isplainobject');

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) {
    var k = keys[x];
    if (!o[k]) return;
    o = o[k];
  }

  return o[keys[keys.length - 1]];
};

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] = [];
      }
      o = 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);
    }
  }

  o[keys[keys.length - 1]] = value;
  return obj;
};

var PARSE_RX = /([^\}:]+)(:([^\}]+))?/;

var EnvVarInterpreter = {
  type: '$',
  replace: function (value, context, parseContext) {
    var m = PARSE_RX.exec(parseContext.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) {
    var m = PARSE_RX.exec(parseContext.value);
    if (!m) {
      return value;
    }

    var newValue = getValue(context.config, m[1]) || m[3];
    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;
  }, {}),

  getEntries: function (config) {
    var entries = [];

    var iter = function (value, prefix) {
      if (!prefix) prefix = [];

      if (Array.isArray(value)) {
        value.forEach(function (arrValue, idx) {
          iter(arrValue, prefix.concat(idx));
        });
      } else if (isPlainObject(value)) {
        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 });
      }
    };

    iter(config);
    return entries;
  },

  firstInnermostInterpreterFromValue: function (value) {
    if (value === null || value === undefined) {
      return null;
    }

    var start = -1;
    var interpreterTypes = Object.keys(ConnieLang.InterpretersByType);

    for (var idx = 1; idx < value.length; ++idx) {
      if (value[idx] === '{' && interpreterTypes.indexOf(value[idx - 1]) !== -1) {
        start = idx - 1;
      } else if (value[idx] === '}' && start !== -1) {
        var interpreter = ConnieLang.InterpretersByType[value[start]];

        var parseContext = {
          type: interpreter.type,
          match: value.slice(start, idx + 1),
          value: value.slice(start + 2, idx),
          start: start,
          end: idx + 1,
          replaceInValue: function (value, context) {
            return interpreter.replace(value, context, parseContext);
          },
        };

        return parseContext;
      }
    }

    return null;
  },

  parse: function (configObj, envObj) {
    var context = {
      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 updated = false;

      entries.forEach(function (e) {
        var interpreter = ConnieLang.firstInnermostInterpreterFromValue(e.value, context);
        if (!interpreter) return;

        var newValue = interpreter.replaceInValue(e.value, context);
        if (newValue !== e.value) {
          e.value = newValue;
          updated = true;
        }
      });

      return updated;
    };

    while (digest());

    var result = {};
    entries.forEach(function (e) {
      setValue(result, e.key, e.value);
    });

    return result;
  },
};

module.exports = ConnieLang;
{
  "name": "connie-lang",
  "description": "Configuration language for connie",
  "version": "0.1.1",
  "homepage": "https://github.com/mattinsler/connie-lang",
  "repository": {
    "type": "git",
    "url": "git://github.com/mattinsler/connie-lang.git"
  },
  "bugs": {
    "url": "https://github.com/mattinsler/connie-lang/issues"
  },
  "main": "lib/connie-lang",
  "scripts": {
    "test": "mocha"
  },
  "engines": {
    "node": ">= 0.10.0"
  },
  "dependencies": {
    "lodash.isplainobject": "2.4.1"
  },
  "devDependencies": {
    "mocha": "2.0.1"
  },
  "keywords": [
    "configuration",
    "connie"
  ]
}
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);
  });
});
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',
      });
    });
  });
});
# 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=