Skip to content

Commit 12958ca

Browse files
committed
fix: enforce eager VDOM cloning in Component constructor to prevent direct access pollution (#8474)
1 parent 08358cb commit 12958ca

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

src/component/Base.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,17 @@ class Component extends Abstract {
280280
_vdom: {}
281281
}
282282

283+
/**
284+
* @param {Object} config
285+
*/
286+
construct(config) {
287+
if (!Object.hasOwn(this, '_vdom') && this._vdom) {
288+
this._vdom = Neo.clone(this._vdom, true)
289+
}
290+
291+
super.construct(config)
292+
}
293+
283294
/**
284295
* Returns true if this Component is fully visible, that is it is not hidden and has no hidden ancestors
285296
*/
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {setup} from '../../setup.mjs';
2+
3+
const appName = 'PrototypePollutionTest_DirectAccess';
4+
5+
setup({
6+
neoConfig: {
7+
allowVdomUpdatesInTests: true,
8+
useDomApiRenderer : true,
9+
workerId: 'main'
10+
},
11+
appConfig: {
12+
name: appName
13+
}
14+
});
15+
16+
import {test, expect} from '@playwright/test';
17+
import Neo from '../../../../src/Neo.mjs';
18+
import * as core from '../../../../src/core/_export.mjs';
19+
import Component from '../../../../src/component/Base.mjs';
20+
21+
test.describe('Neo.component.Base Direct _vdom Access Prototype Pollution', () => {
22+
23+
test('Direct access to _vdom in afterSetId should NOT pollute prototype', async () => {
24+
// Define a component that accesses this._vdom DIRECTLY in afterSetId.
25+
// This bypasses the getter safeguard.
26+
class DirectAccessComponent extends Component {
27+
static config = {
28+
className: 'Test.DirectAccessComponent',
29+
ntype : 'test-direct-access-component',
30+
_vdom: {
31+
tag: 'div',
32+
cls: ['test-class']
33+
}
34+
}
35+
36+
afterSetId(value, oldValue) {
37+
super.afterSetId(value, oldValue);
38+
// DIRECT ACCESS to the private backing property.
39+
// This is the anti-pattern we want to guard against.
40+
this._vdom.id = value;
41+
}
42+
}
43+
44+
const PollutedClass = Neo.setupClass(DirectAccessComponent);
45+
46+
// 1. Create Instance 1
47+
const instance1 = Neo.create(PollutedClass, {
48+
appName,
49+
id: 'instance-direct-1'
50+
});
51+
52+
expect(instance1.vdom.id).toBe('instance-direct-1');
53+
54+
// 2. Create Instance 2
55+
const instance2 = Neo.create(PollutedClass, {
56+
appName,
57+
id: 'instance-direct-2'
58+
});
59+
60+
expect(instance2.vdom.id).toBe('instance-direct-2');
61+
62+
// 3. Verify Prototype Purity
63+
// If the safeguard works (e.g. eager cloning in construct),
64+
// the prototype should be clean.
65+
expect(PollutedClass.prototype._vdom.id).toBeUndefined();
66+
67+
instance1.destroy();
68+
instance2.destroy();
69+
});
70+
});

0 commit comments

Comments
 (0)