Skip to content

Commit 62071e8

Browse files
authored
fix(utils): prevent prototype pollution in deepMerge (#2630)
1 parent b9ce077 commit 62071e8

2 files changed

Lines changed: 29 additions & 2 deletions

File tree

packages/utils/src/__tests__/utils.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,31 @@ describe('Service/Utilies', () => {
604604
e: 2,
605605
});
606606
});
607+
608+
it('should ignore prototype pollution keys while merging objects', () => {
609+
delete (Object.prototype as any).polluted;
610+
611+
const input1 = {};
612+
const input2 = JSON.parse('{"__proto__":{"polluted":"yes"},"constructor":{"prototype":{"polluted":"yes"}},"prototype":{"polluted":"yes"}}');
613+
614+
const output = deepMerge(input1, input2);
615+
616+
expect(output).toEqual({});
617+
expect((Object.prototype as any).polluted).toBeUndefined();
618+
expect(({} as any).polluted).toBeUndefined();
619+
});
620+
621+
it('should not recursively merge into inherited target properties', () => {
622+
const inheritedOptions = { inherited: true };
623+
const target = Object.create({ options: inheritedOptions });
624+
const source = { options: { own: true } };
625+
626+
const output = deepMerge(target, source);
627+
628+
expect(output.options).toEqual({ own: true });
629+
expect(Object.prototype.hasOwnProperty.call(output, 'options')).toBe(true);
630+
expect(inheritedOptions).toEqual({ inherited: true });
631+
});
607632
});
608633

609634
describe('emptyObject() method', () => {

packages/utils/src/utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { AnyFunction } from './models/types.js';
22

3+
const unsafeMergeKeys = new Set(['__proto__', 'constructor', 'prototype']);
4+
35
/**
46
* 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
57
* @param inputArray
@@ -107,8 +109,8 @@ export function deepMerge(target: any, ...sources: any[]): any {
107109

108110
if (isObject(target) && isObject(source)) {
109111
Object.keys(source).forEach((prop) => {
110-
if (source.hasOwnProperty(prop)) {
111-
if (prop in target) {
112+
if (Object.prototype.hasOwnProperty.call(source, prop) && !unsafeMergeKeys.has(prop)) {
113+
if (Object.prototype.hasOwnProperty.call(target, prop)) {
112114
// handling merging of two properties with equal names
113115
if (typeof (target as any)[prop] !== 'object') {
114116
(target as any)[prop] = (source as any)[prop];

0 commit comments

Comments
 (0)