@@ -25,9 +25,14 @@ const {
2525 has : localReflectHas ,
2626 defineProperty : localReflectDefineProperty ,
2727 setPrototypeOf : localReflectSetPrototypeOf ,
28- getOwnPropertyDescriptor : localReflectGetOwnPropertyDescriptor
28+ getOwnPropertyDescriptor : localReflectGetOwnPropertyDescriptor ,
29+ ownKeys : localReflectOwnKeys
2930} = localReflect ;
3031
32+ const localObjectGetOwnPropertySymbols = localObject . getOwnPropertySymbols ;
33+ const localObjectGetOwnPropertyDescriptors = localObject . getOwnPropertyDescriptors ;
34+ const localObjectAssign = localObject . assign ;
35+
3136const speciesSymbol = Symbol . species ;
3237const globalPromise = global . Promise ;
3338class localPromise extends globalPromise { }
@@ -64,6 +69,99 @@ Symbol.for = function(key) {
6469 return originalSymbolFor ( keyStr ) ;
6570} ;
6671
72+ /*
73+ * Cross-realm symbol extraction protection
74+ *
75+ * Even with Symbol.for overridden, cross-realm symbols can be extracted from
76+ * host objects exposed to the sandbox (e.g., Buffer.prototype) via:
77+ * Object.getOwnPropertySymbols(Buffer.prototype).find(s => s.description === 'nodejs.util.inspect.custom')
78+ *
79+ * Fix: Override Object.getOwnPropertySymbols and Reflect.ownKeys to replace
80+ * dangerous cross-realm symbols with sandbox-local equivalents in results.
81+ */
82+ const realSymbolCustomInspect = originalSymbolFor ( 'nodejs.util.inspect.custom' ) ;
83+ const realSymbolRejection = originalSymbolFor ( 'nodejs.rejection' ) ;
84+
85+ function isDangerousSymbol ( sym ) {
86+ return sym === realSymbolCustomInspect || sym === realSymbolRejection ;
87+ }
88+
89+ localObject . getOwnPropertySymbols = function getOwnPropertySymbols ( obj ) {
90+ const symbols = apply ( localObjectGetOwnPropertySymbols , localObject , [ obj ] ) ;
91+ const result = [ ] ;
92+ let j = 0 ;
93+ for ( let i = 0 ; i < symbols . length ; i ++ ) {
94+ if ( typeof symbols [ i ] !== 'symbol' || ! isDangerousSymbol ( symbols [ i ] ) ) {
95+ localReflectDefineProperty ( result , j ++ , {
96+ __proto__ : null ,
97+ value : symbols [ i ] ,
98+ writable : true ,
99+ enumerable : true ,
100+ configurable : true
101+ } ) ;
102+ }
103+ }
104+ return result ;
105+ } ;
106+
107+ localReflect . ownKeys = function ownKeys ( obj ) {
108+ const keys = apply ( localReflectOwnKeys , localReflect , [ obj ] ) ;
109+ const result = [ ] ;
110+ let j = 0 ;
111+ for ( let i = 0 ; i < keys . length ; i ++ ) {
112+ if ( typeof keys [ i ] !== 'symbol' || ! isDangerousSymbol ( keys [ i ] ) ) {
113+ localReflectDefineProperty ( result , j ++ , {
114+ __proto__ : null ,
115+ value : keys [ i ] ,
116+ writable : true ,
117+ enumerable : true ,
118+ configurable : true
119+ } ) ;
120+ }
121+ }
122+ return result ;
123+ } ;
124+
125+ /*
126+ * Object.getOwnPropertyDescriptors uses the internal [[OwnPropertyKeys]] which
127+ * bypasses our Reflect.ownKeys override. The result object has dangerous symbols
128+ * as property keys, which can then be leaked via Object.assign/Object.defineProperties
129+ * to a Proxy whose set/defineProperty trap captures the key.
130+ */
131+ localObject . getOwnPropertyDescriptors = function getOwnPropertyDescriptors ( obj ) {
132+ const descs = apply ( localObjectGetOwnPropertyDescriptors , localObject , [ obj ] ) ;
133+ localReflectDeleteProperty ( descs , realSymbolCustomInspect ) ;
134+ localReflectDeleteProperty ( descs , realSymbolRejection ) ;
135+ return descs ;
136+ } ;
137+
138+ /*
139+ * Object.assign uses internal [[OwnPropertyKeys]] on source objects, bypassing our
140+ * Reflect.ownKeys override. If a source (bridge proxy) has an enumerable dangerous-symbol
141+ * property, the symbol is passed to the target's [[Set]] which could be a user Proxy trap.
142+ */
143+ localObject . assign = function assign ( target ) {
144+ if ( target === null || target === undefined ) {
145+ throw new LocalError ( 'Cannot convert undefined or null to object' ) ;
146+ }
147+ const to = localObject ( target ) ;
148+ for ( let s = 1 ; s < arguments . length ; s ++ ) {
149+ const source = arguments [ s ] ;
150+ if ( source === null || source === undefined ) continue ;
151+ const from = localObject ( source ) ;
152+ const keys = apply ( localReflectOwnKeys , localReflect , [ from ] ) ;
153+ for ( let i = 0 ; i < keys . length ; i ++ ) {
154+ const key = keys [ i ] ;
155+ if ( typeof key === 'symbol' && isDangerousSymbol ( key ) ) continue ;
156+ const desc = apply ( localReflectGetOwnPropertyDescriptor , localReflect , [ from , key ] ) ;
157+ if ( desc && desc . enumerable === true ) {
158+ to [ key ] = from [ key ] ;
159+ }
160+ }
161+ }
162+ return to ;
163+ } ;
164+
67165const resetPromiseSpecies = ( p ) => {
68166 if ( p instanceof globalPromise && ! [ globalPromise , localPromise ] . includes ( p . constructor [ speciesSymbol ] ) ) {
69167 Object . defineProperty ( p . constructor , speciesSymbol , { value : localPromise } ) ;
0 commit comments