diff --git a/packages/utils/__tests__/lodash-adapter.spec.ts b/packages/utils/__tests__/lodash-adapter.spec.ts index 9d3422aa7d7..23857393cc4 100644 --- a/packages/utils/__tests__/lodash-adapter.spec.ts +++ b/packages/utils/__tests__/lodash-adapter.spec.ts @@ -1,6 +1,6 @@ import { lodashUtil } from '../src/lodash-adapter'; -const { extent, uniq, clamp, isEqual, cloneDeep } = lodashUtil; +const { extent, uniq, clamp, isEqual, cloneDeep, merge, mergeWith } = lodashUtil; describe('lodash-adapter', () => { describe('extent', () => { @@ -75,4 +75,59 @@ describe('lodash-adapter', () => { expect(cloned.b).not.toBe(obj.b); }); }); + + describe('merge - prototype pollution protection', () => { + afterEach(() => { + // Clean up any prototype pollution that may have occurred + // @ts-ignore + delete Object.prototype['polluted']; + }); + + it('should not pollute Object.prototype via __proto__', () => { + const malicious = JSON.parse('{"__proto__":{"polluted":"PWNED"}}'); + merge({}, malicious); + expect(({} as any).polluted).toBeUndefined(); + expect(Object.prototype.hasOwnProperty.call(Object.prototype, 'polluted')).toBe(false); + }); + + it('should not pollute Object.prototype via constructor', () => { + const malicious = JSON.parse('{"constructor":{"prototype":{"polluted":"PWNED"}}}'); + merge({}, malicious); + expect(({} as any).polluted).toBeUndefined(); + }); + + it('should still merge normal properties correctly', () => { + const target = { a: 1 }; + const source = { b: 2, c: { d: 3 } }; + const result = merge(target, source); + expect(result).toEqual({ a: 1, b: 2, c: { d: 3 } }); + }); + }); + + describe('mergeWith - prototype pollution protection', () => { + afterEach(() => { + // @ts-ignore + delete Object.prototype['polluted']; + }); + + it('should not pollute Object.prototype via __proto__', () => { + const malicious = JSON.parse('{"__proto__":{"polluted":"PWNED"}}'); + mergeWith({}, malicious, (targetVal: unknown, srcVal: unknown) => srcVal); + expect(({} as any).polluted).toBeUndefined(); + expect(Object.prototype.hasOwnProperty.call(Object.prototype, 'polluted')).toBe(false); + }); + + it('should still merge normal properties with customizer', () => { + const target = { a: 1, b: [1, 2] }; + const source = { b: [3, 4], c: 5 }; + const result = mergeWith(target, source, (targetVal: unknown, srcVal: unknown) => { + if (Array.isArray(targetVal) && Array.isArray(srcVal)) { + return targetVal.concat(srcVal); + } + return undefined; + }); + expect(result.b).toEqual([1, 2, 3, 4]); + expect(result.c).toBe(5); + }); + }); }); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 749e3674809..c8a609ab479 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,3 +3,10 @@ packages: - 'packages/*' # website - 'site' +allowBuilds: + '@parcel/watcher': set this to true or false + '@swc/core': set this to true or false + core-js: set this to true or false + core-js-pure: set this to true or false + esbuild: set this to true or false + gl: set this to true or false