@@ -219,7 +219,7 @@ export function createSafeReflect(securityLevel: SecurityLevel): typeof Reflect
219219
220220 // Wrap Reflect.construct to block Function constructors
221221 if ( typeof value === 'function' && prop === 'construct' ) {
222- return function ( ctorTarget : unknown , args : unknown [ ] , newTarget ?: unknown ) {
222+ const safeConstruct = function ( ctorTarget : unknown , args : unknown [ ] , newTarget ?: unknown ) {
223223 // Block Function, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction constructors
224224 // Intentional empty functions to obtain constructor references
225225 // eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -243,6 +243,17 @@ export function createSafeReflect(securityLevel: SecurityLevel): typeof Reflect
243243 newTarget as new ( ...args : unknown [ ] ) => unknown ,
244244 ) ;
245245 } ;
246+ // Wrap so the returned function cannot itself be walked to the host Function constructor.
247+ return createSecureProxy ( safeConstruct ) ;
248+ }
249+
250+ // SECURITY: never hand back the RAW native Reflect methods. Each is a host-realm function
251+ // whose prototype chain reaches the host Function constructor, and Reflect.get /
252+ // Reflect.getOwnPropertyDescriptor are reflection primitives whose STRING-LITERAL key
253+ // argument bypasses the AST computed-key guard. Wrapping in a secure proxy blocks
254+ // `.constructor` on the method and keeps every result behind the membrane.
255+ if ( typeof value === 'function' ) {
256+ return createSecureProxy ( value ) ;
246257 }
247258
248259 return value ;
@@ -663,7 +674,23 @@ export function createSecureProxy<T extends object>(target: T, options: SecurePr
663674 const propName = typeof property === 'symbol' ? property . toString ( ) : property ;
664675 const descriptor = Reflect . getOwnPropertyDescriptor ( target , property ) ;
665676
666- // Must return actual descriptor for non-configurable properties (proxy invariant)
677+ // Mirror the get trap: a non-configurable, non-writable data property must be reported
678+ // with its exact value (proxy invariant), so an object/function value cannot be wrapped
679+ // or hidden and would cross the barrier unwrapped via `descriptor.value`. Deny the read
680+ // instead (throwing satisfies the invariant); primitives are inert and safe to report.
681+ if ( descriptor && ! descriptor . configurable && descriptor . writable === false && 'value' in descriptor ) {
682+ const exactValue : unknown = descriptor . value ;
683+ if ( exactValue !== null && ( typeof exactValue === 'object' || typeof exactValue === 'function' ) ) {
684+ throw createSafeError (
685+ `Security violation: Access to property descriptor for '${ String ( propName ) } ' is blocked. ` +
686+ `This property cannot be exposed without breaking the sandbox barrier.` ,
687+ 'SecurityError' ,
688+ ) ;
689+ }
690+ return descriptor ;
691+ }
692+
693+ // Must return actual descriptor for other non-configurable properties (proxy invariant)
667694 if ( descriptor && ! descriptor . configurable ) {
668695 return descriptor ;
669696 }
@@ -683,6 +710,15 @@ export function createSecureProxy<T extends object>(target: T, options: SecurePr
683710 return undefined ;
684711 }
685712
713+ // Wrap object/function values so a descriptor read cannot hand back a raw reference that
714+ // the get trap would have proxied (a configurable property's value may be safely wrapped).
715+ if ( descriptor && 'value' in descriptor ) {
716+ const value : unknown = descriptor . value ;
717+ if ( value !== null && ( typeof value === 'object' || typeof value === 'function' ) ) {
718+ return { ...descriptor , value : proxyWithDepth ( value as object , depth + 1 ) } ;
719+ }
720+ }
721+
686722 return descriptor ;
687723 } ,
688724
0 commit comments