Skip to content

Commit

Permalink
Fix for prototype pollution vulnerability
Browse files Browse the repository at this point in the history
  • Loading branch information
apphp committed Apr 6, 2024
1 parent fdcb176 commit 7e347a2
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 7 deletions.
27 changes: 21 additions & 6 deletions dist/object-resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,20 +241,33 @@ const fetchLastNestedProperty = function (obj, path) {
* @param {*} value - The value to set for the nested property.
*/
const setNestedProperty = function (obj, path, value) {
const keys = Array.isArray(path) ? path : path.split('.');
let keys = path;
let current = obj;

if (typeof keys === 'string') {
keys = keys.split('.');
}

if (!Array.isArray(keys)) {
throw new Error('Path must be a string or an array');
}

for (let i = 0; i < keys.length; i++) {
const key = keys[i];

// Prevent prototype pollution
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
throw new Error('Invalid property key');
}

// Check if the current property is an array and if the key has array notation
const isArray = Array.isArray(current);
const isArrayNotation = key.includes('[') && key.endsWith(']');
const isNumericKey = /^\d+$/.test(key.replace(/\[.*\]/, ''));

if (isArray && isArrayNotation && isNumericKey) {
const [arrayKey, indexKey] = key.split(/\[|\]/).filter(Boolean);
const index = parseInt(indexKey);
const index = parseInt(indexKey, 10);

while (current[arrayKey].length <= index) {
current[arrayKey].push(null); // Ensure the array is long enough
Expand All @@ -264,16 +277,18 @@ const setNestedProperty = function (obj, path, value) {
// Last key in the path, set the value
current[arrayKey][index] = value;
} else {
// Continue into the nested object
current = current[arrayKey][index] = current[arrayKey][index] || {};
// Prevent undefined objects in the path
if (!current[arrayKey][index]) current[arrayKey][index] = {};
current = current[arrayKey][index];
}
} else {
if (i === keys.length - 1) {
// Last key in the path, set the value
current[key] = value;
} else {
// Continue into the nested object
current = current[key] = current[key] || {};
// Prevent undefined objects in the path
if (!current[key]) current[key] = {};
current = current[key];
}
}
}
Expand Down
39 changes: 38 additions & 1 deletion tests/setNestedProperty.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,45 @@
const {setNestedProperty: setNestedPropertyTest} = require('../dist/object-resolver');

describe('Test function setNestedProperty', () => {
// Test cases for setNestedProperty

let obj;

beforeEach(() => {
obj = {
existing: { property: 'value' },
arrayProperty: [{ nestedArray: 'nestedValue' }]
};
});

test('Should throw an error when trying to modify __proto__', () => {
const obj = {};
expect(() => setNestedPropertyTest(obj, '__proto__.polluted', 'yes')).toThrow('Invalid property key');
});

test('Should throw an error for non-string and non-array path', () => {
expect(() => setNestedPropertyTest(obj, null, 'newValue')).toThrow('Path must be a string or an array');
expect(() => setNestedPropertyTest(obj, 42, 'newValue')).toThrow('Path must be a string or an array');
expect(() => setNestedPropertyTest(obj, {}, 'newValue')).toThrow('Path must be a string or an array');
});

test('Should throw an error when trying to modify prototype', () => {
expect(() => setNestedPropertyTest(obj, 'prototype.polluted', 'yes')).toThrow('Invalid property key');
});

test('Should throw an error when trying to modify constructor', () => {
expect(() => setNestedPropertyTest(obj, 'constructor.polluted', 'yes')).toThrow('Invalid property key');
});

test('Sets a value at an existing index in an array', () => {
const obj = {
numbers: [1, 2, 3]
};

setNestedPropertyTest(obj, 'numbers.1', 99);
expect(obj.numbers[1]).toBe(99);
});

// Test cases for setNestedProperty
test('Should set a deeply nested property', () => {
const obj = {};
setNestedPropertyTest(obj, 'user.profile.name', 'John Doe');
Expand Down

0 comments on commit 7e347a2

Please sign in to comment.