diff --git a/packages/near-membrane-shared/src/Function.ts b/packages/near-membrane-shared/src/Function.ts index e18ab8c8..433b38d1 100644 --- a/packages/near-membrane-shared/src/Function.ts +++ b/packages/near-membrane-shared/src/Function.ts @@ -22,10 +22,6 @@ import type { ProxyTarget, ProxyTrapInvokers } from './types'; // references to the enum from the generated JavaScript. import { ProxyHandlerTraps } from './types'; -export function noop() { - // No operation performed. -} - export function isProxyMaskedFunction(value: any): boolean { // To extract the flag value of a blue near-membrane proxy we must perform // a two step handshake. First, we trigger the "has" trap for the @@ -38,6 +34,10 @@ export function isProxyMaskedFunction(value: any): boolean { ); } +export function noop() { + // No operation performed. +} + export function proxyMaskFunction( func: Function, maskFunc: T, @@ -45,14 +45,21 @@ export function proxyMaskFunction( ): T { let applyTrapInvoker = ReflectApply; let constructTrapInvoker = ReflectConstruct; + let definePropertyTrapInvoker = ReflectDefineProperty; let getTrapInvoker = (ReflectGet as ProxyTrapInvokers['get'])!; + let getOwnPropertyDescriptorTrapInvoker = ReflectGetOwnPropertyDescriptor; let hasTrapInvoker = ReflectHas; + let setTrapInvoker = ReflectSet; if (trapInvokers) { ({ apply: applyTrapInvoker = ReflectApply, construct: constructTrapInvoker = ReflectConstruct, + defineProperty: definePropertyTrapInvoker = ReflectDefineProperty, get: getTrapInvoker = (ReflectGet as ProxyTrapInvokers['get'])!, + getOwnPropertyDescriptor: + getOwnPropertyDescriptorTrapInvoker = ReflectGetOwnPropertyDescriptor, has: hasTrapInvoker = ReflectHas, + set: setTrapInvoker = ReflectSet, } = trapInvokers); } let handshakeFlag = false; @@ -79,7 +86,7 @@ export function proxyMaskFunction( if (key === LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL) { throw new TypeErrorCtor(ERR_ILLEGAL_PROPERTY_ACCESS); } - return ReflectDefineProperty(target, key, desc); + return definePropertyTrapInvoker(target, key, desc); }, deleteProperty(target: ProxyTarget, key: PropertyKey) { lastProxyTrapCalled = ProxyHandlerTraps.GetOwnPropertyDescriptor; @@ -111,7 +118,7 @@ export function proxyMaskFunction( }, getOwnPropertyDescriptor(target: ProxyTarget, key: PropertyKey) { lastProxyTrapCalled = ProxyHandlerTraps.GetOwnPropertyDescriptor; - const result = ReflectGetOwnPropertyDescriptor(target, key); + const result = getOwnPropertyDescriptorTrapInvoker(target, key); // Getting forged descriptors of handshake properties is not allowed. if (result && key === LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL) { throw new TypeErrorCtor(ERR_ILLEGAL_PROPERTY_ACCESS); @@ -158,7 +165,7 @@ export function proxyMaskFunction( if (key === LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL) { throw new TypeErrorCtor(ERR_ILLEGAL_PROPERTY_ACCESS); } - return ReflectSet(target, key, value, receiver); + return setTrapInvoker(target, key, value, receiver); }, setPrototypeOf(target: ProxyTarget, proto: object | null) { lastProxyTrapCalled = ProxyHandlerTraps.SetPrototypeOf; diff --git a/packages/near-membrane-shared/src/__tests__/Function.spec.js b/packages/near-membrane-shared/src/__tests__/Function.spec.js index 5577171c..9e1edf54 100644 --- a/packages/near-membrane-shared/src/__tests__/Function.spec.js +++ b/packages/near-membrane-shared/src/__tests__/Function.spec.js @@ -33,9 +33,63 @@ describe('Function', () => { function mask() { return 'A'; } + mask.x = 1; + mask[Symbol('y')] = 2; + Reflect.defineProperty(mask, 'z', { + configurable: true, + enumerable: false, + value: 3, + writable: true, + }); const proxyMasked = proxyMaskFunction(() => 'a', mask); expect(proxyMasked.name).toBe(mask.name); expect(proxyMasked()).toBe('a'); + expect(Reflect.ownKeys(proxyMasked)).toEqual(Reflect.ownKeys(mask)); + expect(Reflect.getOwnPropertyDescriptor(proxyMasked, 'z')).toEqual( + Reflect.getOwnPropertyDescriptor(mask, 'z') + ); + }); + + it('should mask extensibility', () => { + function mask() { + return 'A'; + } + function frozenMask() { + return 'frozen'; + } + Object.freeze(frozenMask); + function sealedMask() { + return 'sealed'; + } + Object.seal(sealedMask); + + const maskProto = Reflect.getPrototypeOf(mask); + const proxyMasked = proxyMaskFunction(() => 'a', mask); + const proxyMaskedFrozen = proxyMaskFunction(() => 'a', frozenMask); + const proxyMaskedSealed = proxyMaskFunction(() => 'a', sealedMask); + + expect(Object.isFrozen(proxyMaskedFrozen)).toBe(true); + expect(Object.isExtensible(proxyMaskedFrozen)).toBe(false); + expect(Object.isFrozen(proxyMaskedSealed)).toBe(false); + expect(Object.isExtensible(proxyMaskedSealed)).toBe(false); + expect(Object.isSealed(proxyMaskedSealed)).toBe(true); + + proxyMasked.x = 1; + expect(proxyMasked.x).toBe(1); + expect(mask.x).toBe(1); + expect(Reflect.deleteProperty(proxyMasked, 'x')).toBe(true); + expect('x' in proxyMasked).toBe(false); + expect('x' in mask).toBe(false); + + expect(Reflect.getPrototypeOf(proxyMasked)).toBe(maskProto); + expect(Reflect.setPrototypeOf(proxyMasked, null)).toBe(true); + expect(Reflect.getPrototypeOf(proxyMasked)).toBe(null); + expect(Reflect.getPrototypeOf(mask)).toBe(null); + Reflect.setPrototypeOf(mask, maskProto); + + expect(Reflect.preventExtensions(proxyMasked)).toBe(true); + expect(Object.isExtensible(proxyMasked)).toBe(false); + expect(Object.isExtensible(mask)).toBe(false); }); it('should convert `this` of `mask` to `func`', () => { @@ -135,7 +189,6 @@ describe('Function', () => { return Reflect.get(target, key, receiver); }, }); - expect(proxyMasked.test).toBe('testResult'); }); @@ -157,7 +210,6 @@ describe('Function', () => { return Reflect.has(target, key); }, }); - expect('test' in proxyMasked).toBe(true); }); @@ -167,6 +219,15 @@ describe('Function', () => { expect(() => { proxyMasked[LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL] = true; }).toThrowError(ERR_ILLEGAL_PROPERTY_ACCESS); + expect(() => { + Reflect.defineProperty(proxyMasked, LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL, { + configurable: true, + enumerable: true, + value: true, + writable: true, + }); + }).toThrowError(ERR_ILLEGAL_PROPERTY_ACCESS); + delete proxyMasked[LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL]; bogusMask[LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL] = true; expect(() => LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL in proxyMasked).toThrowError( ERR_ILLEGAL_PROPERTY_ACCESS @@ -180,6 +241,7 @@ describe('Function', () => { LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL ) ).toThrowError(ERR_ILLEGAL_PROPERTY_ACCESS); + delete bogusMask[LOCKER_NEAR_MEMBRANE_PROXY_MASKED_SYMBOL]; }); }); }); diff --git a/packages/near-membrane-shared/src/types.ts b/packages/near-membrane-shared/src/types.ts index 3ab05b11..32a898dd 100644 --- a/packages/near-membrane-shared/src/types.ts +++ b/packages/near-membrane-shared/src/types.ts @@ -22,13 +22,16 @@ export interface ProxyTrapInvokers { // We can add more trap invokers as needed. apply?: typeof Reflect.apply; construct?: typeof Reflect.construct; + defineProperty?: typeof Reflect.defineProperty; get?: ( target: T, propertyKey: P, receiver?: unknown, handshake?: boolean ) => P extends keyof T ? T[P] : any; + getOwnPropertyDescriptor?: typeof Reflect.getOwnPropertyDescriptor; has?: typeof Reflect.has; + set?: typeof Reflect.set; } export type Setter = (value: any) => void; // eslint-disable-next-line no-shadow