11import { getRuntimeExposeMutableGlobal } from "../common/global-exposure" ;
2+ import { setGlobalValue } from "../common/global-access" ;
23
34const __runtimeExposeMutableGlobal = getRuntimeExposeMutableGlobal ( ) ;
45
@@ -8,12 +9,15 @@ const __initialCwd =
89 typeof __bridgeSetupConfig . initialCwd === "string"
910 ? __bridgeSetupConfig . initialCwd
1011 : "/" ;
11- const __jsonPayloadLimitBytes =
12+
13+ // Set payload limit defaults on globalThis — read at call time by v8.deserialize,
14+ // overridable via __runtimeApplyConfig for context snapshot restore
15+ globalThis . __runtimeJsonPayloadLimitBytes =
1216 typeof __bridgeSetupConfig . jsonPayloadLimitBytes === "number" &&
1317 Number . isFinite ( __bridgeSetupConfig . jsonPayloadLimitBytes )
1418 ? Math . max ( 0 , Math . floor ( __bridgeSetupConfig . jsonPayloadLimitBytes ) )
1519 : 4 * 1024 * 1024 ;
16- const __payloadLimitErrorCode =
20+ globalThis . __runtimePayloadLimitErrorCode =
1721 typeof __bridgeSetupConfig . payloadLimitErrorCode === "string" &&
1822 __bridgeSetupConfig . payloadLimitErrorCode . length > 0
1923 ? __bridgeSetupConfig . payloadLimitErrorCode
@@ -237,12 +241,15 @@ if (__moduleCache) {
237241 ) ;
238242 } ,
239243 deserialize : function ( buffer : Buffer ) {
244+ // Read limits from globals at call time (not captured at setup) for snapshot compatibility
245+ const limit = globalThis . __runtimeJsonPayloadLimitBytes ?? 4 * 1024 * 1024 ;
246+ const errorCode = globalThis . __runtimePayloadLimitErrorCode ?? "ERR_SANDBOX_PAYLOAD_TOO_LARGE" ;
240247 // Check raw buffer size BEFORE allocating the decoded string
241- if ( buffer . length > __jsonPayloadLimitBytes ) {
248+ if ( buffer . length > limit ) {
242249 throw new Error (
243- __payloadLimitErrorCode +
250+ errorCode +
244251 ": v8.deserialize exceeds " +
245- String ( __jsonPayloadLimitBytes ) +
252+ String ( limit ) +
246253 " bytes" ,
247254 ) ;
248255 }
@@ -269,3 +276,176 @@ if (__moduleCache) {
269276
270277__runtimeExposeMutableGlobal ( "_pendingModules" , { } ) ;
271278__runtimeExposeMutableGlobal ( "_currentModule" , { dirname : __initialCwd } ) ;
279+
280+ // Post-restore config application — called after bridge IIFE to apply
281+ // per-session config (timing mitigation, payload limits). Enables context
282+ // snapshot reuse: the IIFE runs once at snapshot creation, this function
283+ // applies session-specific config after restore.
284+ globalThis . __runtimeApplyConfig = function ( config : {
285+ timingMitigation ?: string ;
286+ frozenTimeMs ?: number ;
287+ payloadLimitBytes ?: number ;
288+ payloadLimitErrorCode ?: string ;
289+ } ) {
290+ // Apply payload limits
291+ if (
292+ typeof config . payloadLimitBytes === "number" &&
293+ Number . isFinite ( config . payloadLimitBytes )
294+ ) {
295+ globalThis . __runtimeJsonPayloadLimitBytes = Math . max (
296+ 0 ,
297+ Math . floor ( config . payloadLimitBytes ) ,
298+ ) ;
299+ }
300+ if (
301+ typeof config . payloadLimitErrorCode === "string" &&
302+ config . payloadLimitErrorCode . length > 0
303+ ) {
304+ globalThis . __runtimePayloadLimitErrorCode =
305+ config . payloadLimitErrorCode ;
306+ }
307+
308+ // Apply timing mitigation freeze
309+ if ( config . timingMitigation === "freeze" ) {
310+ const frozenTimeMs =
311+ typeof config . frozenTimeMs === "number" &&
312+ Number . isFinite ( config . frozenTimeMs )
313+ ? config . frozenTimeMs
314+ : Date . now ( ) ;
315+ const frozenDateNow = ( ) => frozenTimeMs ;
316+
317+ // Freeze Date.now
318+ try {
319+ Object . defineProperty ( Date , "now" , {
320+ value : frozenDateNow ,
321+ configurable : false ,
322+ writable : false ,
323+ } ) ;
324+ } catch {
325+ Date . now = frozenDateNow ;
326+ }
327+
328+ // Patch Date constructor so new Date().getTime() returns degraded time
329+ const OrigDate = Date ;
330+ const FrozenDate = function Date (
331+ this : InstanceType < DateConstructor > ,
332+ ...args : unknown [ ]
333+ ) {
334+ if ( new . target ) {
335+ if ( args . length === 0 ) {
336+ return new OrigDate ( frozenTimeMs ) ;
337+ }
338+ // @ts -expect-error — spread forwarding to variadic Date constructor
339+ return new OrigDate ( ...args ) ;
340+ }
341+ return OrigDate ( ) ;
342+ } as unknown as DateConstructor ;
343+ Object . defineProperty ( FrozenDate , "prototype" , {
344+ value : OrigDate . prototype ,
345+ writable : false ,
346+ configurable : false ,
347+ } ) ;
348+ FrozenDate . now = frozenDateNow ;
349+ FrozenDate . parse = OrigDate . parse ;
350+ FrozenDate . UTC = OrigDate . UTC ;
351+ Object . defineProperty ( FrozenDate , "now" , {
352+ value : frozenDateNow ,
353+ configurable : false ,
354+ writable : false ,
355+ } ) ;
356+ try {
357+ Object . defineProperty ( globalThis , "Date" , {
358+ value : FrozenDate ,
359+ configurable : false ,
360+ writable : false ,
361+ } ) ;
362+ } catch {
363+ ( globalThis as Record < string , unknown > ) . Date = FrozenDate ;
364+ }
365+
366+ // Freeze performance.now
367+ const frozenPerformanceNow = ( ) => 0 ;
368+ const origPerf = globalThis . performance ;
369+ const frozenPerf = Object . create ( null ) as Record < string , unknown > ;
370+ if ( typeof origPerf !== "undefined" && origPerf !== null ) {
371+ const src = origPerf as unknown as Record < string , unknown > ;
372+ for ( const key of Object . getOwnPropertyNames (
373+ Object . getPrototypeOf ( origPerf ) ?? origPerf ,
374+ ) ) {
375+ if ( key !== "now" ) {
376+ try {
377+ const val = src [ key ] ;
378+ if ( typeof val === "function" ) {
379+ frozenPerf [ key ] = val . bind ( origPerf ) ;
380+ } else {
381+ frozenPerf [ key ] = val ;
382+ }
383+ } catch {
384+ /* skip inaccessible properties */
385+ }
386+ }
387+ }
388+ }
389+ Object . defineProperty ( frozenPerf , "now" , {
390+ value : frozenPerformanceNow ,
391+ configurable : false ,
392+ writable : false ,
393+ } ) ;
394+ Object . freeze ( frozenPerf ) ;
395+ try {
396+ Object . defineProperty ( globalThis , "performance" , {
397+ value : frozenPerf ,
398+ configurable : false ,
399+ writable : false ,
400+ } ) ;
401+ } catch {
402+ ( globalThis as Record < string , unknown > ) . performance = frozenPerf ;
403+ }
404+
405+ // Harden SharedArrayBuffer removal
406+ const OrigSAB = globalThis . SharedArrayBuffer ;
407+ if ( typeof OrigSAB === "function" ) {
408+ try {
409+ const proto = OrigSAB . prototype ;
410+ if ( proto ) {
411+ for ( const key of [
412+ "byteLength" ,
413+ "slice" ,
414+ "grow" ,
415+ "maxByteLength" ,
416+ "growable" ,
417+ ] ) {
418+ try {
419+ Object . defineProperty ( proto , key , {
420+ get ( ) {
421+ throw new TypeError (
422+ "SharedArrayBuffer is not available in sandbox" ,
423+ ) ;
424+ } ,
425+ configurable : false ,
426+ } ) ;
427+ } catch {
428+ /* property may not exist or be non-configurable */
429+ }
430+ }
431+ }
432+ } catch {
433+ /* best-effort prototype neutering */
434+ }
435+ }
436+ try {
437+ Object . defineProperty ( globalThis , "SharedArrayBuffer" , {
438+ value : undefined ,
439+ configurable : false ,
440+ writable : false ,
441+ enumerable : false ,
442+ } ) ;
443+ } catch {
444+ Reflect . deleteProperty ( globalThis , "SharedArrayBuffer" ) ;
445+ setGlobalValue ( "SharedArrayBuffer" , undefined ) ;
446+ }
447+ }
448+
449+ // Clean up — one-shot function
450+ delete globalThis . __runtimeApplyConfig ;
451+ } ;
0 commit comments