Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion packages/utils/__tests__/lodash-adapter.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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);
});
});
});
7 changes: 7 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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