diff --git a/packages/utils/src/__tests__/utils.spec.ts b/packages/utils/src/__tests__/utils.spec.ts index ceae67ca7..91e4ab736 100644 --- a/packages/utils/src/__tests__/utils.spec.ts +++ b/packages/utils/src/__tests__/utils.spec.ts @@ -604,6 +604,31 @@ describe('Service/Utilies', () => { e: 2, }); }); + + it('should ignore prototype pollution keys while merging objects', () => { + delete (Object.prototype as any).polluted; + + const input1 = {}; + const input2 = JSON.parse('{"__proto__":{"polluted":"yes"},"constructor":{"prototype":{"polluted":"yes"}},"prototype":{"polluted":"yes"}}'); + + const output = deepMerge(input1, input2); + + expect(output).toEqual({}); + expect((Object.prototype as any).polluted).toBeUndefined(); + expect(({} as any).polluted).toBeUndefined(); + }); + + it('should not recursively merge into inherited target properties', () => { + const inheritedOptions = { inherited: true }; + const target = Object.create({ options: inheritedOptions }); + const source = { options: { own: true } }; + + const output = deepMerge(target, source); + + expect(output.options).toEqual({ own: true }); + expect(Object.prototype.hasOwnProperty.call(output, 'options')).toBe(true); + expect(inheritedOptions).toEqual({ inherited: true }); + }); }); describe('emptyObject() method', () => { diff --git a/packages/utils/src/utils.ts b/packages/utils/src/utils.ts index 720d197b2..ad772602e 100644 --- a/packages/utils/src/utils.ts +++ b/packages/utils/src/utils.ts @@ -1,5 +1,7 @@ import type { AnyFunction } from './models/types.js'; +const unsafeMergeKeys = new Set(['__proto__', 'constructor', 'prototype']); + /** * Add an item to an array only when the item does not exists, when the item is an object we will be using their "id" to compare * @param inputArray @@ -107,8 +109,8 @@ export function deepMerge(target: any, ...sources: any[]): any { if (isObject(target) && isObject(source)) { Object.keys(source).forEach((prop) => { - if (source.hasOwnProperty(prop)) { - if (prop in target) { + if (Object.prototype.hasOwnProperty.call(source, prop) && !unsafeMergeKeys.has(prop)) { + if (Object.prototype.hasOwnProperty.call(target, prop)) { // handling merging of two properties with equal names if (typeof (target as any)[prop] !== 'object') { (target as any)[prop] = (source as any)[prop];