Skip to content

Commit

Permalink
added security tests, changed to yarn, bump version
Browse files Browse the repository at this point in the history
  • Loading branch information
mattinsler committed Aug 17, 2020
1 parent 65d0843 commit ef376d4
Show file tree
Hide file tree
Showing 6 changed files with 519 additions and 215 deletions.
84 changes: 51 additions & 33 deletions lib/connie-lang.js
Original file line number Diff line number Diff line change
@@ -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];
Expand All @@ -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);
Expand All @@ -36,63 +40,77 @@ 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 });
}
};

iter(config);
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);
Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -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;
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -23,5 +23,8 @@
"devDependencies": {
"mocha": "2.0.1"
},
"keywords": ["configuration", "connie"]
}
"keywords": [
"configuration",
"connie"
]
}
20 changes: 20 additions & 0 deletions test/connie-lang.security.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading

0 comments on commit ef376d4

Please # to comment.