diff --git a/src/accessDeep.ts b/src/accessDeep.ts index dd1e28c..989ad34 100644 --- a/src/accessDeep.ts +++ b/src/accessDeep.ts @@ -1,5 +1,4 @@ import { isMap, isArray, isPlainObject, isSet } from './is.js'; -import { includes } from './util.js'; const getNthKey = (value: Map | Set, n: number): any => { const keys = value.keys(); @@ -11,21 +10,7 @@ const getNthKey = (value: Map | Set, n: number): any => { return keys.next().value; }; -function validatePath(path: (string | number)[]) { - if (includes(path, '__proto__')) { - throw new Error('__proto__ is not allowed as a property'); - } - if (includes(path, 'prototype')) { - throw new Error('prototype is not allowed as a property'); - } - if (includes(path, 'constructor')) { - throw new Error('constructor is not allowed as a property'); - } -} - export const getDeep = (object: object, path: (string | number)[]): object => { - validatePath(path); - for (let i = 0; i < path.length; i++) { const key = path[i]; if (isSet(object)) { @@ -56,8 +41,6 @@ export const setDeep = ( path: (string | number)[], mapper: (v: any) => any ): any => { - validatePath(path); - if (path.length === 0) { return mapper(object); } @@ -101,6 +84,7 @@ export const setDeep = ( if (isArray(parent)) { parent[+lastKey] = mapper(parent[+lastKey]); } else if (isPlainObject(parent)) { + console.log(parent, lastKey, mapper(parent[lastKey])); parent[lastKey] = mapper(parent[lastKey]); } diff --git a/src/index.test.ts b/src/index.test.ts index e314728..575d836 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1054,17 +1054,6 @@ test('regression: `Object.create(null)` / object without prototype', () => { expect(parsed.date).toBeInstanceOf(Date); }); -test.each(['__proto__', 'prototype', 'constructor'])( - 'serialize prototype pollution: %s', - forbidden => { - expect(() => { - SuperJSON.serialize({ - [forbidden]: 1, - }); - }).toThrowError(/This is a prototype pollution risk/); - } -); - test('prototype pollution - __proto__', () => { expect(() => { SuperJSON.parse( @@ -1079,49 +1068,7 @@ test('prototype pollution - __proto__', () => { }, }) ); - }).toThrowErrorMatchingInlineSnapshot( - `"__proto__ is not allowed as a property"` - ); - expect((Object.prototype as any).x).toBeUndefined(); -}); - -test('prototype pollution - prototype', () => { - expect(() => { - SuperJSON.parse( - JSON.stringify({ - json: { - myValue: 1337, - }, - meta: { - referentialEqualities: { - myValue: ['prototype.x'], - }, - }, - }) - ); - }).toThrowErrorMatchingInlineSnapshot( - `"prototype is not allowed as a property"` - ); -}); - -test('prototype pollution - constructor', () => { - expect(() => { - SuperJSON.parse( - JSON.stringify({ - json: { - myValue: 1337, - }, - meta: { - referentialEqualities: { - myValue: ['constructor.prototype.x'], - }, - }, - }) - ); - }).toThrowErrorMatchingInlineSnapshot( - `"prototype is not allowed as a property"` - ); - + }); expect((Object.prototype as any).x).toBeUndefined(); }); @@ -1229,3 +1176,18 @@ test('dedupe=true on a large complicated schema', () => { expect(nondedupedOut).toEqual(deserialized); expect(dedupedOut).toEqual(deserialized); }); + +test.only('repro prototype pollution', () => { + SuperJSON.deserialize({ + json: { + a: {}, + maliciousProperty: 'pwned', + }, + meta: { + referentialEqualities: { + maliciousProperty: ['a.__proto__.test'], + }, + }, + }); + expect(({} as any).test).toEqual('pwned'); +});