diff --git a/packages/core/session/src/json.ts b/packages/core/session/src/json.ts index eb277d299b..f7609a1f46 100644 --- a/packages/core/session/src/json.ts +++ b/packages/core/session/src/json.ts @@ -12,20 +12,35 @@ */ export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue } +/** Whether a realm-owned intrinsic prototype names and points back to its constructor. */ +function hasIntrinsicConstructor(prototype: object, name: 'Array' | 'Object'): boolean { + const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor') + const constructor: unknown = descriptor?.value + return typeof constructor === 'function' + && constructor.name === name + && constructor.prototype === prototype +} + +/** Whether a candidate is one realm's intrinsic `Object.prototype`. */ +function isIntrinsicObjectPrototype(value: object): boolean { + return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, 'Object') +} + /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */ function hasPlainArrayPrototype(value: unknown[]): boolean { const prototype: unknown = Object.getPrototypeOf(value) - if (!Array.isArray(prototype)) return false + if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, 'Array')) return false const objectPrototype: unknown = Object.getPrototypeOf(prototype) - return objectPrototype !== null - && !Array.isArray(objectPrototype) - && Object.getPrototypeOf(objectPrototype) === null + return typeof objectPrototype === 'object' + && objectPrototype !== null + && isIntrinsicObjectPrototype(objectPrototype) } /** Whether an object is a plain or null-prototype record from any JavaScript realm. */ function hasPlainObjectPrototype(value: object): boolean { const prototype: unknown = Object.getPrototypeOf(value) - return prototype === null || Object.getPrototypeOf(prototype) === null + return prototype === null + || typeof prototype === 'object' && isIntrinsicObjectPrototype(prototype) } /** Return every JSON-visible object key, or reject own data JSON would discard. */ diff --git a/packages/core/session/tests/json.spec.ts b/packages/core/session/tests/json.spec.ts index 06fd37b9d2..90b3ec6928 100644 --- a/packages/core/session/tests/json.spec.ts +++ b/packages/core/session/tests/json.spec.ts @@ -94,6 +94,8 @@ describe('snapshotJsonValue', () => { Object.defineProperty(symbolDecorated, Symbol('extra'), { value: true }) const hiddenObject = Object.defineProperty({}, 'hidden', { value: true }) const symbolObject = { [Symbol('extra')]: true } + const customPrototype = Object.create(null) as Record + const customPrototypeObject = Object.assign(Object.create(customPrototype) as Record, { value: 1 }) const forgedPrototype: unknown[] = [] Object.setPrototypeOf(forgedPrototype, null) const forgedArray = [1] @@ -117,6 +119,7 @@ describe('snapshotJsonValue', () => { expect(snapshotJsonValue(symbolDecorated)).toBeUndefined() expect(snapshotJsonValue(hiddenObject)).toBeUndefined() expect(snapshotJsonValue(symbolObject)).toBeUndefined() + expect(snapshotJsonValue(customPrototypeObject)).toBeUndefined() expect(snapshotJsonValue(forgedArray)).toBeUndefined() expect(snapshotJsonValue(cyclic)).toBeUndefined() expect(snapshotJsonValue([undefined])).toBeUndefined() @@ -188,6 +191,8 @@ describe('isJsonValue', () => { Object.defineProperty(symbolDecorated, Symbol('extra'), { value: true }) const hiddenObject = Object.defineProperty({}, 'hidden', { value: true }) const symbolObject = { [Symbol('extra')]: true } + const customPrototype = Object.create(null) as Record + const customPrototypeObject = Object.assign(Object.create(customPrototype) as Record, { value: 1 }) const forgedPrototype: unknown[] = [] Object.setPrototypeOf(forgedPrototype, null) const forgedArray = [1] @@ -201,6 +206,7 @@ describe('isJsonValue', () => { expect(isJsonValue(symbolDecorated)).toBe(false) expect(isJsonValue(hiddenObject)).toBe(false) expect(isJsonValue(symbolObject)).toBe(false) + expect(isJsonValue(customPrototypeObject)).toBe(false) expect(isJsonValue(forgedArray)).toBe(false) expect(isJsonValue(new ExoticArray(1))).toBe(false) expect(isJsonValue([undefined])).toBe(false)