diff --git a/benchmarks/bundle-size/clone.spec.ts b/benchmarks/bundle-size/clone.spec.ts index a1483afe9..4f840f3e8 100644 --- a/benchmarks/bundle-size/clone.spec.ts +++ b/benchmarks/bundle-size/clone.spec.ts @@ -9,11 +9,11 @@ describe('clone bundle size', () => { it('es-toolkit', async () => { const bundleSize = await getBundleSize('es-toolkit', 'clone'); - expect(bundleSize).toMatchInlineSnapshot(`1001`); + expect(bundleSize).toMatchInlineSnapshot(`1140`); }); it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'clone'); - expect(bundleSize).toMatchInlineSnapshot(`2527`); + expect(bundleSize).toMatchInlineSnapshot(`2625`); }); }); diff --git a/benchmarks/bundle-size/cloneDeep.spec.ts b/benchmarks/bundle-size/cloneDeep.spec.ts index 8c4087314..d47c2821f 100644 --- a/benchmarks/bundle-size/cloneDeep.spec.ts +++ b/benchmarks/bundle-size/cloneDeep.spec.ts @@ -9,11 +9,11 @@ describe('cloneDeep bundle size', () => { it('es-toolkit', async () => { const bundleSize = await getBundleSize('es-toolkit', 'cloneDeep'); - expect(bundleSize).toMatchInlineSnapshot(`3400`); + expect(bundleSize).toMatchInlineSnapshot(`3509`); }); it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'cloneDeep'); - expect(bundleSize).toMatchInlineSnapshot(`3840`); + expect(bundleSize).toMatchInlineSnapshot(`3949`); }); }); diff --git a/benchmarks/bundle-size/find.spec.ts b/benchmarks/bundle-size/find.spec.ts index b0da817d6..522187c1a 100644 --- a/benchmarks/bundle-size/find.spec.ts +++ b/benchmarks/bundle-size/find.spec.ts @@ -9,6 +9,6 @@ describe('find bundle size', () => { it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'find'); - expect(bundleSize).toMatchInlineSnapshot(`9166`); + expect(bundleSize).toMatchInlineSnapshot(`9275`); }); }); diff --git a/benchmarks/bundle-size/findKey.spec.ts b/benchmarks/bundle-size/findKey.spec.ts index baafcbb3f..1e6138aa9 100644 --- a/benchmarks/bundle-size/findKey.spec.ts +++ b/benchmarks/bundle-size/findKey.spec.ts @@ -14,6 +14,6 @@ describe('findKey bundle size', () => { it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'findKey'); - expect(bundleSize).toMatchInlineSnapshot(`8579`); + expect(bundleSize).toMatchInlineSnapshot(`8688`); }); }); diff --git a/benchmarks/bundle-size/fp/pick.spec.ts b/benchmarks/bundle-size/fp/pick.spec.ts index 931b8842b..41e3d0049 100644 --- a/benchmarks/bundle-size/fp/pick.spec.ts +++ b/benchmarks/bundle-size/fp/pick.spec.ts @@ -5,7 +5,7 @@ describe('fp/pick bundle size', () => { it('es-toolkit/fp', async () => { expect( await getBundleSizeFromScript('import { pick } from "es-toolkit/fp"; console.log(pick)') - ).toMatchInlineSnapshot(`180`); + ).toMatchInlineSnapshot(`278`); }); it('lodash/fp/pick', async () => { diff --git a/benchmarks/bundle-size/mapKeys.spec.ts b/benchmarks/bundle-size/mapKeys.spec.ts index 72abfe306..eb17b51b0 100644 --- a/benchmarks/bundle-size/mapKeys.spec.ts +++ b/benchmarks/bundle-size/mapKeys.spec.ts @@ -14,6 +14,6 @@ describe('mapKeys bundle size', () => { it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'mapKeys'); - expect(bundleSize).toMatchInlineSnapshot(`8592`); + expect(bundleSize).toMatchInlineSnapshot(`8701`); }); }); diff --git a/benchmarks/bundle-size/mapValues.spec.ts b/benchmarks/bundle-size/mapValues.spec.ts index 2e389a09e..3dc383a4e 100644 --- a/benchmarks/bundle-size/mapValues.spec.ts +++ b/benchmarks/bundle-size/mapValues.spec.ts @@ -9,11 +9,11 @@ describe('mapValues bundle size', () => { it('es-toolkit', async () => { const bundleSize = await getBundleSize('es-toolkit', 'mapValues'); - expect(bundleSize).toMatchInlineSnapshot(`138`); + expect(bundleSize).toMatchInlineSnapshot(`237`); }); it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'mapValues'); - expect(bundleSize).toMatchInlineSnapshot(`8592`); + expect(bundleSize).toMatchInlineSnapshot(`8800`); }); }); diff --git a/benchmarks/bundle-size/merge.spec.ts b/benchmarks/bundle-size/merge.spec.ts index aaa62a2f5..d20b355ea 100644 --- a/benchmarks/bundle-size/merge.spec.ts +++ b/benchmarks/bundle-size/merge.spec.ts @@ -14,6 +14,6 @@ describe('merge bundle size', () => { it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'merge'); - expect(bundleSize).toMatchInlineSnapshot(`6381`); + expect(bundleSize).toMatchInlineSnapshot(`6629`); }); }); diff --git a/benchmarks/bundle-size/mergeWith.spec.ts b/benchmarks/bundle-size/mergeWith.spec.ts index 45d529836..bf51c47f8 100644 --- a/benchmarks/bundle-size/mergeWith.spec.ts +++ b/benchmarks/bundle-size/mergeWith.spec.ts @@ -14,6 +14,6 @@ describe('mergeWith bundle size', () => { it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'mergeWith'); - expect(bundleSize).toMatchInlineSnapshot(`6325`); + expect(bundleSize).toMatchInlineSnapshot(`6573`); }); }); diff --git a/benchmarks/bundle-size/omit.spec.ts b/benchmarks/bundle-size/omit.spec.ts index 2ec9c5e0e..25b5badb1 100644 --- a/benchmarks/bundle-size/omit.spec.ts +++ b/benchmarks/bundle-size/omit.spec.ts @@ -14,6 +14,6 @@ describe('omit bundle size', () => { it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'omit'); - expect(bundleSize).toMatchInlineSnapshot(`8877`); + expect(bundleSize).toMatchInlineSnapshot(`9183`); }); }); diff --git a/benchmarks/bundle-size/zipObject.spec.ts b/benchmarks/bundle-size/zipObject.spec.ts index d39aa0f3e..ab970076d 100644 --- a/benchmarks/bundle-size/zipObject.spec.ts +++ b/benchmarks/bundle-size/zipObject.spec.ts @@ -14,6 +14,6 @@ describe('zipObject bundle size', () => { it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'zipObject'); - expect(bundleSize).toMatchInlineSnapshot(`281`); + expect(bundleSize).toMatchInlineSnapshot(`376`); }); }); diff --git a/benchmarks/bundle-size/zipObjectDeep.spec.ts b/benchmarks/bundle-size/zipObjectDeep.spec.ts index eee8e7e2b..0db554f80 100644 --- a/benchmarks/bundle-size/zipObjectDeep.spec.ts +++ b/benchmarks/bundle-size/zipObjectDeep.spec.ts @@ -9,6 +9,6 @@ describe('zipObjectDeep bundle size', () => { it('es-toolkit/compat', async () => { const bundleSize = await getBundleSize('es-toolkit/compat', 'zipObjectDeep'); - expect(bundleSize).toMatchInlineSnapshot(`3431`); + expect(bundleSize).toMatchInlineSnapshot(`3526`); }); }); diff --git a/src/compat/_internal/assignValue.spec.ts b/src/compat/_internal/assignValue.spec.ts new file mode 100644 index 000000000..45a7cb052 --- /dev/null +++ b/src/compat/_internal/assignValue.spec.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest'; +import { assignValue } from './assignValue'; + +describe('assignValue', () => { + it('should assign a value to an object', () => { + const object: Record = {}; + assignValue(object, 'a', 1); + expect(object.a).toBe(1); + }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const object: Record = {}; + assignValue(object, '__proto__', { isAdmin: true }); + + expect(Object.hasOwn(object, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(object)).toBe(Object.prototype); + expect((object as any).isAdmin).toBeUndefined(); + }); +}); diff --git a/src/compat/_internal/assignValue.ts b/src/compat/_internal/assignValue.ts index 9884451cf..3a0f9e0f8 100644 --- a/src/compat/_internal/assignValue.ts +++ b/src/compat/_internal/assignValue.ts @@ -3,6 +3,15 @@ import { eq } from '../util/eq.ts'; export const assignValue = (object: any, key: PropertyKey, value: any): void => { const objValue = object[key]; if (!(Object.hasOwn(object, key) && eq(objValue, value)) || (value === undefined && !(key in object))) { - object[key] = value; + if (key === '__proto__') { + Object.defineProperty(object, key, { + configurable: true, + enumerable: true, + value, + writable: true, + }); + } else { + object[key] = value; + } } }; diff --git a/src/compat/object/assign.spec.ts b/src/compat/object/assign.spec.ts index 764c0dfa0..8a4ffb812 100644 --- a/src/compat/object/assign.spec.ts +++ b/src/compat/object/assign.spec.ts @@ -111,4 +111,13 @@ describe('assign', () => { const result = assign({}, ...values); expect(result).toEqual({ workId: undefined, exerciseId: '1' }); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = assign({}, parsed); + + expect(Object.hasOwn(result, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/assign.ts b/src/compat/object/assign.ts index 8fa526ea5..6e4527340 100644 --- a/src/compat/object/assign.ts +++ b/src/compat/object/assign.ts @@ -145,7 +145,16 @@ function assignImpl(object: any, source: any): any { for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (!(key in object) || !eq(object[key], source[key])) { - object[key] = source[key]; + if (key === '__proto__') { + Object.defineProperty(object, key, { + configurable: true, + enumerable: true, + value: source[key], + writable: true, + }); + } else { + object[key] = source[key]; + } } } } diff --git a/src/compat/object/assignIn.spec.ts b/src/compat/object/assignIn.spec.ts index 09ae0b17d..6d33411f2 100644 --- a/src/compat/object/assignIn.spec.ts +++ b/src/compat/object/assignIn.spec.ts @@ -111,4 +111,13 @@ describe('assignIn', () => { const result = assignIn({}, ...values); expect(result).toEqual({ workId: undefined, exerciseId: '1' }); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = assignIn({}, parsed); + + expect(Object.hasOwn(result, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/assignIn.ts b/src/compat/object/assignIn.ts index 2bcfd3ee0..ff7f573e3 100644 --- a/src/compat/object/assignIn.ts +++ b/src/compat/object/assignIn.ts @@ -146,7 +146,16 @@ function assignInImpl(object: any, source: any): any { for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (!(key in object) || !eq(object[key], source[key])) { - object[key] = source[key]; + if (key === '__proto__') { + Object.defineProperty(object, key, { + configurable: true, + enumerable: true, + value: source[key], + writable: true, + }); + } else { + object[key] = source[key]; + } } } } diff --git a/src/compat/object/assignInWith.spec.ts b/src/compat/object/assignInWith.spec.ts index 1607e2490..a7886049b 100644 --- a/src/compat/object/assignInWith.spec.ts +++ b/src/compat/object/assignInWith.spec.ts @@ -20,4 +20,13 @@ describe('assignInWith', () => { const result = assignInWith(target, source); expect(result).toEqual({ a: 1, b: 2 }); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = assignInWith({}, parsed); + + expect(Object.hasOwn(result as any, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/assignInWith.ts b/src/compat/object/assignInWith.ts index 4c81432f9..839fa53d2 100644 --- a/src/compat/object/assignInWith.ts +++ b/src/compat/object/assignInWith.ts @@ -202,7 +202,16 @@ function assignInWithImpl( const newValue = getValueToAssign?.(objValue, srcValue, key, object, source) ?? srcValue; if (!(key in object) || !eq(objValue, newValue)) { - object[key] = newValue; + if (key === '__proto__') { + Object.defineProperty(object, key, { + configurable: true, + enumerable: true, + value: newValue, + writable: true, + }); + } else { + object[key] = newValue; + } } } } diff --git a/src/compat/object/assignWith.spec.ts b/src/compat/object/assignWith.spec.ts index 62ac319ec..23c5ed1c4 100644 --- a/src/compat/object/assignWith.spec.ts +++ b/src/compat/object/assignWith.spec.ts @@ -20,4 +20,13 @@ describe('assignWith', () => { const result = assignWith(target, source); expect(result).toEqual({ a: 1, b: 2 }); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = assignWith({}, parsed); + + expect(Object.hasOwn(result as any, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/assignWith.ts b/src/compat/object/assignWith.ts index ca49543ee..b4e246396 100644 --- a/src/compat/object/assignWith.ts +++ b/src/compat/object/assignWith.ts @@ -202,7 +202,16 @@ function assignWithImpl( const newValue = getValueToAssign?.(objValue, srcValue, key, object, source) ?? srcValue; if (!(key in object) || !eq(objValue, newValue)) { - object[key] = newValue; + if (key === '__proto__') { + Object.defineProperty(object, key, { + configurable: true, + enumerable: true, + value: newValue, + writable: true, + }); + } else { + object[key] = newValue; + } } } } diff --git a/src/compat/object/clone.spec.ts b/src/compat/object/clone.spec.ts index 7d4cdb755..adff9dda3 100644 --- a/src/compat/object/clone.spec.ts +++ b/src/compat/object/clone.spec.ts @@ -546,4 +546,13 @@ describe('clone', () => { expect(clone(value)).toEqual({}); }); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const cloned = clone(parsed); + + expect(Object.hasOwn(cloned, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(cloned)).toBe(Object.prototype); + expect((cloned as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/clone.ts b/src/compat/object/clone.ts index 2be9cc75e..59439d419 100644 --- a/src/compat/object/clone.ts +++ b/src/compat/object/clone.ts @@ -207,7 +207,16 @@ function isCloneableObject(object: any): boolean { function copyOwnProperties(target: any, source: any): void { for (const key in source) { if (Object.hasOwn(source, key)) { - target[key] = source[key]; + if (key === '__proto__') { + Object.defineProperty(target, key, { + configurable: true, + enumerable: true, + value: source[key], + writable: true, + }); + } else { + target[key] = source[key]; + } } } } diff --git a/src/compat/object/cloneDeep.spec.ts b/src/compat/object/cloneDeep.spec.ts index 458d2be4b..cc80616af 100644 --- a/src/compat/object/cloneDeep.spec.ts +++ b/src/compat/object/cloneDeep.spec.ts @@ -264,4 +264,13 @@ describe('cloneDeep', () => { expect(actual).toEqual(expected); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const cloned = cloneDeep(parsed); + + expect(Object.hasOwn(cloned, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(cloned)).toBe(Object.prototype); + expect((cloned as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/mapValues.spec.ts b/src/compat/object/mapValues.spec.ts index af93cb586..84901bcda 100644 --- a/src/compat/object/mapValues.spec.ts +++ b/src/compat/object/mapValues.spec.ts @@ -43,4 +43,13 @@ describe('mapValues', () => { it('should return empty object when object is undefined', () => { expect(mapValues(undefined)).toEqual({}); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = mapValues(parsed, v => v); + + expect(Object.hasOwn(result, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/omit.spec.ts b/src/compat/object/omit.spec.ts index de7a91884..b7dc65a37 100644 --- a/src/compat/object/omit.spec.ts +++ b/src/compat/object/omit.spec.ts @@ -117,4 +117,13 @@ describe('omit', () => { expect('3' in result).toBe(false); expect('4' in result).toBe(false); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = omit(parsed, 'x'); + + expect(Object.hasOwn(result, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/omit.ts b/src/compat/object/omit.ts index d49f43d4f..f933b8df6 100644 --- a/src/compat/object/omit.ts +++ b/src/compat/object/omit.ts @@ -132,7 +132,16 @@ function shallowCloneInOmit(obj: T): Partial { for (let i = 0; i < keysToCopy.length; i++) { const key = keysToCopy[i]; - result[key] = obj[key]; + if (key === '__proto__') { + Object.defineProperty(result, key, { + configurable: true, + enumerable: true, + value: obj[key], + writable: true, + }); + } else { + result[key] = obj[key]; + } } return result; @@ -144,13 +153,24 @@ function deepCloneInOmit(obj: T): Partial { for (let i = 0; i < keysToCopy.length; i++) { const key = keysToCopy[i]; - result[key] = cloneDeepWith(obj[key], valueToClone => { + const clonedValue = cloneDeepWith(obj[key], valueToClone => { if (isPlainObject(valueToClone)) { return undefined; } return valueToClone; }); + + if (key === '__proto__') { + Object.defineProperty(result, key, { + configurable: true, + enumerable: true, + value: clonedValue, + writable: true, + }); + } else { + result[key] = clonedValue; + } } return result; diff --git a/src/compat/object/omitBy.spec.ts b/src/compat/object/omitBy.spec.ts index 00ef66ef4..c068164e1 100644 --- a/src/compat/object/omitBy.spec.ts +++ b/src/compat/object/omitBy.spec.ts @@ -144,4 +144,13 @@ describe('omitBy', () => { expect(actual.length).toBe(104); expect((actual as any)[sym]).toBe('error'); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = omitBy(parsed, (_val, key) => key === 'x'); + + expect(Object.hasOwn(result, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/omitBy.ts b/src/compat/object/omitBy.ts index 8a4f895fd..7149c3a23 100644 --- a/src/compat/object/omitBy.ts +++ b/src/compat/object/omitBy.ts @@ -93,7 +93,16 @@ export function omitBy( const value = object[key as keyof typeof object]; if (!predicate(value, key, object)) { - result[key] = value; + if (key === '__proto__') { + Object.defineProperty(result, key, { + configurable: true, + enumerable: true, + value, + writable: true, + }); + } else { + result[key] = value; + } } } diff --git a/src/compat/object/pick.spec.ts b/src/compat/object/pick.spec.ts index a44dc0d1e..5a04633e5 100644 --- a/src/compat/object/pick.spec.ts +++ b/src/compat/object/pick.spec.ts @@ -143,4 +143,13 @@ describe('compat/pick', () => { expect(Reflect.ownKeys(result)).toEqual([]); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = pick(parsed, ['x', '__proto__']); + + expect(Object.hasOwn(result, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/pick.ts b/src/compat/object/pick.ts index bef98095f..ab2887aa6 100644 --- a/src/compat/object/pick.ts +++ b/src/compat/object/pick.ts @@ -105,7 +105,16 @@ export function pick( } if (typeof key === 'string' && Object.hasOwn(object, key)) { - result[key] = object[key]; + if (key === '__proto__') { + Object.defineProperty(result, key, { + configurable: true, + enumerable: true, + value: object[key], + writable: true, + }); + } else { + result[key] = object[key]; + } } else { set(result, key, value); } diff --git a/src/compat/object/pickBy.spec.ts b/src/compat/object/pickBy.spec.ts index 95ef13732..eb752e71b 100644 --- a/src/compat/object/pickBy.spec.ts +++ b/src/compat/object/pickBy.spec.ts @@ -218,4 +218,13 @@ describe('pickBy', () => { expect(count).toBe(1); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = pickBy(parsed, (_val, key) => key === '__proto__' || key === 'x'); + + expect(Object.hasOwn(result, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/compat/object/pickBy.ts b/src/compat/object/pickBy.ts index bbba52923..e05de7d54 100644 --- a/src/compat/object/pickBy.ts +++ b/src/compat/object/pickBy.ts @@ -140,7 +140,16 @@ export function pickBy( const value = obj[key as keyof typeof obj]; if (predicate(value, key, obj)) { - result[key] = value; + if (key === '__proto__') { + Object.defineProperty(result, key, { + configurable: true, + enumerable: true, + value, + writable: true, + }); + } else { + result[key] = value; + } } } diff --git a/src/object/clone.spec.ts b/src/object/clone.spec.ts index eeae4c442..fa99491e5 100644 --- a/src/object/clone.spec.ts +++ b/src/object/clone.spec.ts @@ -287,4 +287,13 @@ describe('clone', () => { expect(clonedInstance.value).toBe(instance.value); expect(clonedInstance.getValue()).toBe(123); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const cloned = clone(parsed); + + expect(Object.hasOwn(cloned, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(cloned)).toBe(Object.prototype); + expect((cloned as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/object/clone.ts b/src/object/clone.ts index 44df70a67..b9390d641 100644 --- a/src/object/clone.ts +++ b/src/object/clone.ts @@ -85,8 +85,16 @@ export function clone(obj: T): T { return newFile; } - if (typeof obj === 'object') { + if (typeof obj === 'object' && obj !== null) { const newObject = Object.create(prototype); + if (Object.hasOwn(obj, '__proto__')) { + Object.defineProperty(newObject, '__proto__', { + configurable: true, + enumerable: true, + value: (obj as any)['__proto__'], + writable: true, + }); + } return Object.assign(newObject, obj); } diff --git a/src/object/cloneDeep.spec.ts b/src/object/cloneDeep.spec.ts index 2644a9235..94683fb1b 100644 --- a/src/object/cloneDeep.spec.ts +++ b/src/object/cloneDeep.spec.ts @@ -504,4 +504,13 @@ describe('cloneDeep', () => { expect(cloned).not.toBe(arr); expect(cloned).toBeInstanceOf(Int32Array); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const cloned = cloneDeep(parsed); + + expect(Object.hasOwn(cloned, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(cloned)).toBe(Object.prototype); + expect((cloned as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/object/cloneDeepWith.ts b/src/object/cloneDeepWith.ts index afab4e0b2..d28e2ef27 100644 --- a/src/object/cloneDeepWith.ts +++ b/src/object/cloneDeepWith.ts @@ -271,7 +271,16 @@ export function copyProperties( const descriptor = Object.getOwnPropertyDescriptor(target, key); if (descriptor == null || descriptor.writable) { - target[key] = cloneDeepWithImpl(source[key], key, objectToClone, stack, cloneValue); + if (key === '__proto__') { + Object.defineProperty(target, key, { + value: cloneDeepWithImpl(source[key], key, objectToClone, stack, cloneValue), + writable: true, + enumerable: true, + configurable: true, + }); + } else { + target[key] = cloneDeepWithImpl(source[key], key, objectToClone, stack, cloneValue); + } } } } diff --git a/src/object/mapValues.spec.ts b/src/object/mapValues.spec.ts index b70045629..6fc02808c 100644 --- a/src/object/mapValues.spec.ts +++ b/src/object/mapValues.spec.ts @@ -18,4 +18,13 @@ describe('mapValues', () => { }) ).toEqual({ a: 11, b: 22, c: 33 }); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = mapValues(parsed, v => v); + + expect(Object.hasOwn(result, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/object/mapValues.ts b/src/object/mapValues.ts index e96a2bc71..04dceccb4 100644 --- a/src/object/mapValues.ts +++ b/src/object/mapValues.ts @@ -27,7 +27,17 @@ export function mapValues( const key = keys[i] as K; const value = object[key]; - result[key] = getNewValue(value, key, object); + const mappedValue = getNewValue(value, key, object); + if (key === '__proto__') { + Object.defineProperty(result, key, { + configurable: true, + enumerable: true, + value: mappedValue, + writable: true, + }); + } else { + result[key] = mappedValue; + } } return result; diff --git a/src/object/pick.spec.ts b/src/object/pick.spec.ts index 8efb5d5c0..a5341f9f8 100644 --- a/src/object/pick.spec.ts +++ b/src/object/pick.spec.ts @@ -32,4 +32,13 @@ describe('pick', () => { expect(Reflect.ownKeys(result)).toEqual([]); }); + + it('should preserve own __proto__ property and not set as prototype', () => { + const parsed = JSON.parse('{"__proto__": {"isAdmin": true}, "x": 1}'); + const result = pick(parsed, ['x', '__proto__']); + + expect(Object.hasOwn(result, '__proto__')).toBe(true); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect((result as any).isAdmin).toBeUndefined(); + }); }); diff --git a/src/object/pick.ts b/src/object/pick.ts index 79481185a..330c73f64 100644 --- a/src/object/pick.ts +++ b/src/object/pick.ts @@ -22,7 +22,16 @@ export function pick, K extends keyof T>(obj: T, k const key = keys[i]; if (Object.hasOwn(obj, key)) { - result[key] = obj[key]; + if (key === '__proto__') { + Object.defineProperty(result, key, { + configurable: true, + enumerable: true, + value: obj[key], + writable: true, + }); + } else { + result[key] = obj[key]; + } } }