diff --git a/lib/connie-lang.js b/lib/connie-lang.js index 1012b33..bab66e3 100644 --- a/lib/connie-lang.js +++ b/lib/connie-lang.js @@ -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; diff --git a/package.json b/package.json index eec94c9..8da3ecc 100644 --- a/package.json +++ b/package.json @@ -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 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/test/test-connie-lang.js b/test/test-connie-lang.js deleted file mode 100644 index 5b76f1f..0000000 --- a/test/test-connie-lang.js +++ /dev/null @@ -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' - }); - }); - }); -}); 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=