Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions lib/nconf/stores/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ function escapeRegExp(string) {
return typeof string === 'string' && string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

// Guard against prototype pollution via dangerous key segments
function isSafeKey(key) {
return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
}

//
// ### function Memory (options)
// #### @options {Object} Options for this instance
Expand Down Expand Up @@ -123,6 +128,9 @@ Memory.prototype.set = function (key, value) {
//
while (path.length > 1) {
key = path.shift();
if (!isSafeKey(key)) {
return false;
}
if (!target[key] || typeof target[key] !== 'object') {
target[key] = {};
}
Expand All @@ -132,6 +140,9 @@ Memory.prototype.set = function (key, value) {

// Set the specified value in the nested JSON structure
key = path.shift();
if (!isSafeKey(key)) {
return false;
}
if (this.parseValues) {
value = common.parseValues.call(common, value);
}
Expand Down Expand Up @@ -212,6 +223,9 @@ Memory.prototype.merge = function (key, value) {
//
while (path.length > 1) {
key = path.shift();
if (!isSafeKey(key)) {
return false;
}
if (!target[key]) {
target[key] = {};
}
Expand All @@ -221,6 +235,9 @@ Memory.prototype.merge = function (key, value) {

// Set the specified value in the nested JSON structure
key = path.shift();
if (!isSafeKey(key)) {
return false;
}

//
// If the current value at the key target is not an `Object`,
Expand Down
48 changes: 48 additions & 0 deletions test/stores/memory-store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,54 @@ describe('nconf/stores/memory', () => {
});
});
});
describe("prototype pollution prevention", () => {
// merge() uses common.path() directly (no _normalizeKey), so __proto__ reaches
// the traversal loop as-is and must be blocked by isSafeKey().
const dangerousKeys = [
'__proto__:polluted',
'constructor:polluted',
'prototype:polluted',
'a:__proto__:polluted',
'a:constructor:polluted',
'a:prototype:polluted',
];

it("merge() should return false and not pollute Object.prototype", () => {
const store = new nconf.Memory();
for (const key of dangerousKeys) {
expect(store.merge(key, { value: 'injected' })).toBe(false);
}
expect(({}).polluted).toBeUndefined();
});

it("nconf.merge() with __proto__ key should not pollute Object.prototype", () => {
nconf.merge('__proto__:polluted', { value: 'yes' });
expect(({}).polluted).toBeUndefined();
});

// set() runs _normalizeKey() first (which replaces '__' input separator),
// so __proto__ is transformed before traversal. Test that no pollution occurs
// regardless of normalisation behaviour.
it("set() should not pollute Object.prototype", () => {
const store = new nconf.Memory();
for (const key of dangerousKeys) {
store.set(key, { value: 'injected' });
}
expect(({}).polluted).toBeUndefined();
});

// With a custom separator config where '__' is NOT the input separator,
// '__proto__' passes through _normalizeKey unchanged and isSafeKey() is
// the only guard — it must return false.
it("set() should return false for dangerous keys when '__' is not the input separator", () => {
const store = new nconf.Memory({ inputSeparator: '-', disableDefaultAccessSeparator: true });
expect(store.set('__proto__:polluted', { value: 'injected' })).toBe(false);
expect(store.set('constructor:polluted', { value: 'injected' })).toBe(false);
expect(store.set('prototype:polluted', { value: 'injected' })).toBe(false);
expect(({}).polluted).toBeUndefined();
});
});

describe("When using the nconf memory store with different logical separator", () => {
var store = new nconf.Memory({ accessSeparator: '||', disableDefaultAccessSeparator: true });

Expand Down