@@ -19,6 +19,18 @@ const DEFAULT_RESOURCE_LIMITS = Object.freeze({
1919 maxHeapUsedBytes : 512 * 1024 * 1024 ,
2020 maxHeapDeltaBytes : 8 * 1024 * 1024 ,
2121} ) ;
22+ const UNSAFE_CONTEXT_KEYS = Object . freeze ( [
23+ 'process' ,
24+ 'global' ,
25+ 'globalThis' ,
26+ 'window' ,
27+ 'document' ,
28+ 'Function' ,
29+ 'eval' ,
30+ 'require' ,
31+ 'module' ,
32+ ] ) ;
33+ const RESERVED_PROTOTYPE_KEYS = Object . freeze ( [ '__proto__' , 'prototype' , 'constructor' ] ) ;
2234
2335function normalizePluginId ( pluginId ) {
2436 return String ( pluginId || '' ) . trim ( ) ;
@@ -32,6 +44,75 @@ function normalizeLimit(value, fallback) {
3244 return numeric ;
3345}
3446
47+ function isPlainObject ( value ) {
48+ if ( ! value || typeof value !== 'object' ) {
49+ return false ;
50+ }
51+ const proto = Object . getPrototypeOf ( value ) ;
52+ return proto === Object . prototype || proto === null ;
53+ }
54+
55+ function sanitizePluginContextValue ( value , depth = 0 ) {
56+ if ( depth > 6 ) {
57+ return null ;
58+ }
59+ if ( value === null ) {
60+ return null ;
61+ }
62+ const valueType = typeof value ;
63+ if ( valueType === 'string' || valueType === 'number' || valueType === 'boolean' ) {
64+ return value ;
65+ }
66+ if ( valueType === 'bigint' ) {
67+ return Number ( value ) ;
68+ }
69+ if ( valueType === 'undefined' || valueType === 'function' || valueType === 'symbol' ) {
70+ return undefined ;
71+ }
72+
73+ if ( Array . isArray ( value ) ) {
74+ const result = [ ] ;
75+ for ( let i = 0 ; i < value . length ; i += 1 ) {
76+ const sanitized = sanitizePluginContextValue ( value [ i ] , depth + 1 ) ;
77+ if ( typeof sanitized === 'undefined' ) {
78+ continue ;
79+ }
80+ result . push ( sanitized ) ;
81+ }
82+ return result ;
83+ }
84+
85+ if ( ! isPlainObject ( value ) ) {
86+ return undefined ;
87+ }
88+
89+ const output = { } ;
90+ const keys = Object . keys ( value ) ;
91+ for ( let i = 0 ; i < keys . length ; i += 1 ) {
92+ const key = String ( keys [ i ] || '' ) . trim ( ) ;
93+ if ( ! key ) {
94+ continue ;
95+ }
96+ if ( UNSAFE_CONTEXT_KEYS . includes ( key ) || RESERVED_PROTOTYPE_KEYS . includes ( key ) ) {
97+ continue ;
98+ }
99+ const sanitized = sanitizePluginContextValue ( value [ key ] , depth + 1 ) ;
100+ if ( typeof sanitized === 'undefined' ) {
101+ continue ;
102+ }
103+ output [ key ] = sanitized ;
104+ }
105+ return output ;
106+ }
107+
108+ function sanitizePluginContext ( context = { } ) {
109+ const sanitized = sanitizePluginContextValue ( context , 0 ) ;
110+ if ( ! sanitized || typeof sanitized !== 'object' ) {
111+ return { } ;
112+ }
113+ return sanitized ;
114+ }
115+
35116function normalizeResourceLimits ( resourceLimits = { } ) {
36117 return Object . freeze ( {
37118 maxHookDurationMs : normalizeLimit ( resourceLimits ?. maxHookDurationMs , DEFAULT_RESOURCE_LIMITS . maxHookDurationMs ) ,
@@ -251,17 +332,70 @@ export default function createPhase19OverlayPluginRegistry({
251332 return pluginRecordMap . get ( normalizedPluginId ) ?? null ;
252333 }
253334
254- function getReadonlyRegistryView ( ) {
255- return createReadonlyFacade ( api , [
256- 'getPlugin' ,
257- 'getPluginState' ,
258- 'getPluginExtensionId' ,
259- 'getPluginMetrics' ,
260- 'listPluginMetrics' ,
261- 'getPluginDiagnostics' ,
262- 'listPluginDiagnostics' ,
263- 'listPlugins' ,
264- ] ) ;
335+ function getReadonlyRegistryView ( pluginId = '' ) {
336+ const normalizedPluginId = normalizePluginId ( pluginId ) ;
337+ return Object . freeze ( {
338+ getPlugin ( requestedPluginId = normalizedPluginId ) {
339+ const requestedId = normalizePluginId ( requestedPluginId ) ;
340+ if ( ! requestedId || requestedId !== normalizedPluginId ) {
341+ return null ;
342+ }
343+ return api . getPlugin ( requestedId ) ;
344+ } ,
345+ getPluginState ( requestedPluginId = normalizedPluginId ) {
346+ const requestedId = normalizePluginId ( requestedPluginId ) ;
347+ if ( ! requestedId || requestedId !== normalizedPluginId ) {
348+ return '' ;
349+ }
350+ return api . getPluginState ( requestedId ) ;
351+ } ,
352+ getPluginExtensionId ( requestedPluginId = normalizedPluginId ) {
353+ const requestedId = normalizePluginId ( requestedPluginId ) ;
354+ if ( ! requestedId || requestedId !== normalizedPluginId ) {
355+ return '' ;
356+ }
357+ return api . getPluginExtensionId ( requestedId ) ;
358+ } ,
359+ getPluginMetrics ( requestedPluginId = normalizedPluginId ) {
360+ const requestedId = normalizePluginId ( requestedPluginId ) ;
361+ if ( ! requestedId || requestedId !== normalizedPluginId ) {
362+ return null ;
363+ }
364+ return api . getPluginMetrics ( requestedId ) ;
365+ } ,
366+ getPluginDiagnostics ( requestedPluginId = normalizedPluginId ) {
367+ const requestedId = normalizePluginId ( requestedPluginId ) ;
368+ if ( ! requestedId || requestedId !== normalizedPluginId ) {
369+ return null ;
370+ }
371+ return api . getPluginDiagnostics ( requestedId ) ;
372+ } ,
373+ listPlugins ( ) {
374+ const plugin = api . getPlugin ( normalizedPluginId ) ;
375+ if ( ! plugin ) {
376+ return Object . freeze ( [ ] ) ;
377+ }
378+ return Object . freeze ( [ {
379+ id : normalizedPluginId ,
380+ state : api . getPluginState ( normalizedPluginId ) ,
381+ extensionId : api . getPluginExtensionId ( normalizedPluginId ) ,
382+ } ] ) ;
383+ } ,
384+ listPluginMetrics ( ) {
385+ const metrics = api . getPluginMetrics ( normalizedPluginId ) ;
386+ if ( ! metrics ) {
387+ return Object . freeze ( [ ] ) ;
388+ }
389+ return Object . freeze ( [ { pluginId : normalizedPluginId , metrics } ] ) ;
390+ } ,
391+ listPluginDiagnostics ( ) {
392+ const diagnostics = api . getPluginDiagnostics ( normalizedPluginId ) ;
393+ if ( ! diagnostics ) {
394+ return Object . freeze ( [ ] ) ;
395+ }
396+ return Object . freeze ( [ diagnostics ] ) ;
397+ } ,
398+ } ) ;
265399 }
266400
267401 function getReadonlyFrameworkView ( ) {
@@ -454,13 +588,19 @@ export default function createPhase19OverlayPluginRegistry({
454588 try {
455589 const previousHookPluginId = activeHookPluginId ;
456590 activeHookPluginId = record . plugin . id ;
591+ const safeContext = sanitizePluginContext ( context ) ;
457592 const lifecycleContext = deepFreezeValue ( {
458- ...context ,
593+ ...safeContext ,
459594 phase,
460595 pluginId : record . plugin . id ,
461596 extensionId : record . extension . id ,
462- registry : getReadonlyRegistryView ( ) ,
597+ registry : getReadonlyRegistryView ( record . plugin . id ) ,
463598 expansionFramework : getReadonlyFrameworkView ( ) ,
599+ security : {
600+ mode : 'isolated' ,
601+ scopedRegistryAccess : true ,
602+ unsafeContextKeysRemoved : UNSAFE_CONTEXT_KEYS ,
603+ } ,
464604 } ) ;
465605 try {
466606 hook ( lifecycleContext ) ;
@@ -697,10 +837,15 @@ export default function createPhase19OverlayPluginRegistry({
697837 normalizedPlugin ,
698838 normalizedPlugin . createOverlayExtension
699839 ? normalizedPlugin . createOverlayExtension ( {
840+ ...sanitizePluginContext ( context ) ,
700841 pluginId,
701- registry : getReadonlyRegistryView ( ) ,
842+ registry : getReadonlyRegistryView ( pluginId ) ,
702843 expansionFramework : getReadonlyFrameworkView ( ) ,
703- ...context ,
844+ security : {
845+ mode : 'isolated' ,
846+ scopedRegistryAccess : true ,
847+ unsafeContextKeysRemoved : UNSAFE_CONTEXT_KEYS ,
848+ } ,
704849 } )
705850 : normalizedPlugin . extension
706851 ) ;
0 commit comments