Skip to content

Commit 3b86be0

Browse files
committed
More complete fix against prototype pollution
first addressed in #384
1 parent e3173b1 commit 3b86be0

File tree

2 files changed

+48
-7
lines changed

2 files changed

+48
-7
lines changed

packages/convict/src/main.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ const fs = require('fs')
99
const parseArgs = require('yargs-parser')
1010
const cloneDeep = require('lodash.clonedeep')
1111

12+
// Forbidden key paths, for protection against prototype pollution
13+
const FORBIDDEN_KEY_PATHS = [
14+
'__proto__',
15+
'this.constructor.prototype',
16+
]
17+
18+
const ALLOWED_OPTION_STRICT = 'strict'
19+
const ALLOWED_OPTION_WARN = 'warn'
20+
1221
function assert(assertion, err_msg) {
1322
if (!assertion) {
1423
throw new Error(err_msg)
@@ -69,9 +78,6 @@ const custom_converters = new Map()
6978

7079
const parsers_registry = {'*': JSON.parse}
7180

72-
const ALLOWED_OPTION_STRICT = 'strict'
73-
const ALLOWED_OPTION_WARN = 'warn'
74-
7581
function flatten(obj, useProperties) {
7682
const stack = Object.keys(obj)
7783
let key
@@ -561,14 +567,18 @@ const convict = function convict(def, opts) {
561567
* exist, they will be initialized to empty objects
562568
*/
563569
set: function(k, v) {
570+
for (const path of FORBIDDEN_KEY_PATHS) {
571+
if (k.startsWith(`${path}.`)) {
572+
return this
573+
}
574+
}
575+
564576
v = coerce(k, v, this._schema, this)
565577
const path = k.split('.')
566578
const childKey = path.pop()
567579
const parentKey = path.join('.')
568-
if (!(parentKey == '__proto__' || parentKey == 'constructor' || parentKey == 'prototype')) {
569-
const parent = walk(this._instance, parentKey, true)
570-
parent[childKey] = v
571-
}
580+
const parent = walk(this._instance, parentKey, true)
581+
parent[childKey] = v
572582
return this
573583
},
574584

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict'
2+
3+
const convict = require('../')
4+
5+
describe('Convict prototype pollution resistance', function() {
6+
7+
test('against __proto__', function() {
8+
const obj = {}
9+
const config = convict(obj)
10+
11+
config.set('__proto__.polluted_proto_root', 'Polluted!')
12+
expect({}).not.toHaveProperty('polluted_proto_root')
13+
14+
config.set('__proto__.nested.polluted_proto_nested', 'Polluted!')
15+
expect({}).not.toHaveProperty('nested')
16+
expect({}).not.toHaveProperty('nested.polluted_proto_nested')
17+
})
18+
19+
test('against this.constructor.prototype', function() {
20+
const obj = {}
21+
const config = convict(obj)
22+
23+
config.set('this.constructor.prototype.polluted_constructor_prototype_root', 'Polluted!')
24+
expect({}).not.toHaveProperty('polluted_constructor_prototype_root')
25+
26+
config.set('this.constructor.prototype.nested.polluted_constructor_prototype_nested', 'Polluted!')
27+
expect({}).not.toHaveProperty('nested')
28+
expect({}).not.toHaveProperty('nested.polluted_constructor_prototype_nested')
29+
})
30+
31+
})

0 commit comments

Comments
 (0)