Skip to content

Commit 79b8085

Browse files
committed
fix: @W-21607885 Block distorted properties from being deleted
1 parent e259627 commit 79b8085

8 files changed

Lines changed: 156 additions & 0 deletions

File tree

packages/near-membrane-base/src/environment.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export class VirtualEnvironment {
6868
distortionCallback,
6969
instrumentation,
7070
liveTargetCallback,
71+
protectDistortions,
7172
revokedProxyCallback,
7273
signSourceCallback,
7374
// eslint-disable-next-line prefer-object-spread
@@ -82,6 +83,7 @@ export class VirtualEnvironment {
8283
distortionCallback,
8384
instrumentation,
8485
liveTargetCallback,
86+
protectDistortions,
8587
revokedProxyCallback,
8688
}
8789
);

packages/near-membrane-base/src/membrane.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ export function createMembraneMarshall(
674674
const {
675675
distortionCallback,
676676
liveTargetCallback,
677+
protectDistortions,
677678
revokedProxyCallback,
678679
// eslint-disable-next-line prefer-object-spread
679680
} = ObjectAssign({ __proto__: null }, options);
@@ -3776,6 +3777,31 @@ export function createMembraneMarshall(
37763777
targetPointer();
37773778
const target = selectedTarget!;
37783779
selectedTarget = undefined;
3780+
if (protectDistortions && distortionCallback) {
3781+
let safeDesc;
3782+
try {
3783+
safeDesc = ReflectGetOwnPropertyDescriptor(target, key);
3784+
} catch (error: any) {
3785+
throw pushErrorAcrossBoundary(error);
3786+
}
3787+
if (safeDesc) {
3788+
ReflectSetPrototypeOf(safeDesc, null);
3789+
const { get: getter, set: setter, value } = safeDesc;
3790+
if (
3791+
(typeof getter === 'function' &&
3792+
distortionCallback(getter) !== getter) ||
3793+
(typeof setter === 'function' &&
3794+
distortionCallback(setter) !== setter) ||
3795+
(((typeof value === 'object' && value !== null) ||
3796+
typeof value === 'function') &&
3797+
distortionCallback(value) !== value)
3798+
) {
3799+
throw pushErrorAcrossBoundary(
3800+
new TypeErrorCtor('Cannot delete property with a distortion.')
3801+
);
3802+
}
3803+
}
3804+
}
37793805
try {
37803806
return ReflectDeleteProperty(target, key);
37813807
} catch (error: any) {

packages/near-membrane-base/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ export interface HooksOptions {
179179
distortionCallback?: DistortionCallback;
180180
instrumentation?: Instrumentation;
181181
liveTargetCallback?: LiveTargetCallback;
182+
protectDistortions?: boolean;
182183
revokedProxyCallback?: RevokedProxyCallback;
183184
}
184185
export interface Instrumentation {
@@ -200,6 +201,7 @@ export interface VirtualEnvironmentOptions {
200201
distortionCallback?: DistortionCallback;
201202
instrumentation?: Instrumentation;
202203
liveTargetCallback?: LiveTargetCallback;
204+
protectDistortions?: boolean;
203205
revokedProxyCallback?: RevokedProxyCallback;
204206
signSourceCallback?: SignSourceCallback;
205207
}

packages/near-membrane-dom/src/browser-realm.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ function createIframeVirtualEnvironment(
7979
keepAlive = true,
8080
liveTargetCallback,
8181
maxPerfMode = false,
82+
protectDistortions = false,
8283
signSourceCallback,
8384
// eslint-disable-next-line prefer-object-spread
8485
} = ObjectAssign({ __proto__: null }, providedOptions) as BrowserEnvironmentOptions;
@@ -115,6 +116,7 @@ function createIframeVirtualEnvironment(
115116
distortionCallback,
116117
instrumentation,
117118
liveTargetCallback,
119+
protectDistortions,
118120
revokedProxyCallback: keepAlive ? revokedProxyCallback : undefined,
119121
signSourceCallback,
120122
});

packages/near-membrane-dom/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ export interface BrowserEnvironmentOptions {
1414
keepAlive?: boolean;
1515
liveTargetCallback?: LiveTargetCallback;
1616
maxPerfMode?: boolean;
17+
protectDistortions?: boolean;
1718
signSourceCallback?: SignSourceCallback;
1819
}

packages/near-membrane-node/src/node-realm.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default function createVirtualEnvironment(
3434
instrumentation,
3535
liveTargetCallback,
3636
maxPerfMode = false,
37+
protectDistortions = false,
3738
signSourceCallback,
3839
} = ObjectAssign({ __proto__: null }, providedOptions) as NodeEnvironmentOptions;
3940
let blueConnector = blueCreateHooksCallbackCache.get(globalObject) as Connector | undefined;
@@ -53,6 +54,7 @@ export default function createVirtualEnvironment(
5354
distortionCallback,
5455
instrumentation,
5556
liveTargetCallback,
57+
protectDistortions,
5658
signSourceCallback,
5759
});
5860
linkIntrinsics(env, globalObject);

packages/near-membrane-node/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ export interface NodeEnvironmentOptions {
1212
instrumentation?: Instrumentation;
1313
liveTargetCallback?: LiveTargetCallback;
1414
maxPerfMode?: boolean;
15+
protectDistortions?: boolean;
1516
signSourceCallback?: SignSourceCallback;
1617
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import createVirtualEnvironment from '@locker/near-membrane-dom';
2+
3+
const originalHostDesc = Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'host');
4+
const { get: hostGetter } = originalHostDesc;
5+
6+
const distortionMap = new Map([[hostGetter, () => null]]);
7+
8+
function distortionCallback(v) {
9+
return distortionMap.get(v) ?? v;
10+
}
11+
12+
describe('protectDistortions', () => {
13+
afterEach(() => {
14+
if (!Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'host')) {
15+
Object.defineProperty(ShadowRoot.prototype, 'host', originalHostDesc);
16+
}
17+
});
18+
19+
describe('when enabled', () => {
20+
it('should throw when deleting a property whose getter is distorted', () => {
21+
expect.assertions(1);
22+
23+
const env = createVirtualEnvironment(window, {
24+
distortionCallback,
25+
liveTargetCallback() {
26+
return true;
27+
},
28+
protectDistortions: true,
29+
globalObjectShape: window,
30+
});
31+
32+
env.evaluate(`
33+
expect(() => {
34+
delete ShadowRoot.prototype.host;
35+
}).toThrowError(TypeError);
36+
`);
37+
});
38+
39+
it('should allow deleting properties that are not distorted', () => {
40+
expect.assertions(2);
41+
42+
const env = createVirtualEnvironment(window, {
43+
distortionCallback,
44+
liveTargetCallback() {
45+
return true;
46+
},
47+
protectDistortions: true,
48+
globalObjectShape: window,
49+
});
50+
51+
env.evaluate(`
52+
const obj = { x: 1 };
53+
expect(delete obj.x).toBe(true);
54+
expect(obj.x).toBe(undefined);
55+
`);
56+
});
57+
58+
it('should allow deleting an undistorted window API', () => {
59+
expect.assertions(3);
60+
61+
const env = createVirtualEnvironment(window, {
62+
distortionCallback,
63+
liveTargetCallback() {
64+
return true;
65+
},
66+
protectDistortions: true,
67+
globalObjectShape: window,
68+
});
69+
70+
env.evaluate(`
71+
expect(delete window.AbortController).toBe(true);
72+
expect(window.AbortController).toBe(undefined);
73+
`);
74+
75+
expect(window.AbortController).not.toBe(undefined);
76+
});
77+
78+
it('should preserve the distorted getter after a failed delete attempt', () => {
79+
expect.assertions(2);
80+
81+
const env = createVirtualEnvironment(window, {
82+
distortionCallback,
83+
liveTargetCallback() {
84+
return true;
85+
},
86+
protectDistortions: true,
87+
globalObjectShape: window,
88+
});
89+
90+
env.evaluate(`
91+
const elm = document.createElement('div');
92+
elm.attachShadow({ mode: 'open' });
93+
expect(() => {
94+
delete ShadowRoot.prototype.host;
95+
}).toThrowError(TypeError);
96+
expect(elm.shadowRoot.host).toBe(null);
97+
`);
98+
});
99+
});
100+
101+
describe('when disabled (default)', () => {
102+
it('should not throw when deleting a property whose getter is distorted', () => {
103+
expect.assertions(1);
104+
105+
const env = createVirtualEnvironment(window, {
106+
distortionCallback,
107+
liveTargetCallback() {
108+
return true;
109+
},
110+
globalObjectShape: window,
111+
});
112+
113+
env.evaluate(`
114+
expect(() => {
115+
delete ShadowRoot.prototype.host;
116+
}).not.toThrow();
117+
`);
118+
});
119+
});
120+
});

0 commit comments

Comments
 (0)