|
344 | 344 | return stub; |
345 | 345 | } |
346 | 346 |
|
| 347 | + // Capture the real module cache for internal use before exposing a read-only view |
| 348 | + const __internalModuleCache = _moduleCache; |
| 349 | + |
347 | 350 | const __require = function require(moduleName) { |
348 | 351 | return _requireFrom(moduleName, _currentModule.dirname); |
349 | 352 | }; |
|
362 | 365 | globalThis.require.resolve = function resolve(moduleName) { |
363 | 366 | return _resolveFrom(moduleName, _currentModule.dirname); |
364 | 367 | }; |
365 | | - globalThis.require.cache = _moduleCache; |
366 | 368 |
|
367 | 369 | function _debugRequire(phase, moduleName, extra) { |
368 | 370 | if (globalThis.__sandboxRequireDebug !== true) { |
|
403 | 405 | const isRelative = name.startsWith('./') || name.startsWith('../'); |
404 | 406 |
|
405 | 407 | // Get cached modules for bare/absolute specifiers up front. |
406 | | - if (!isRelative && _moduleCache[name]) { |
| 408 | + if (!isRelative && __internalModuleCache[name]) { |
407 | 409 | _debugRequire('cache-hit', name, name); |
408 | | - return _moduleCache[name]; |
| 410 | + return __internalModuleCache[name]; |
409 | 411 | } |
410 | 412 |
|
411 | 413 | // Special handling for fs module |
412 | 414 | if (name === 'fs') { |
413 | | - if (_moduleCache['fs']) return _moduleCache['fs']; |
| 415 | + if (__internalModuleCache['fs']) return __internalModuleCache['fs']; |
414 | 416 | const fsModule = globalThis.bridge?.fs || globalThis.bridge?.default || globalThis._fsModule || {}; |
415 | | - _moduleCache['fs'] = fsModule; |
| 417 | + __internalModuleCache['fs'] = fsModule; |
416 | 418 | _debugRequire('loaded', name, 'fs-special'); |
417 | 419 | return fsModule; |
418 | 420 | } |
419 | 421 |
|
420 | 422 | // Special handling for fs/promises module |
421 | 423 | if (name === 'fs/promises') { |
422 | | - if (_moduleCache['fs/promises']) return _moduleCache['fs/promises']; |
| 424 | + if (__internalModuleCache['fs/promises']) return __internalModuleCache['fs/promises']; |
423 | 425 | // Get fs module first, then extract promises |
424 | 426 | const fsModule = _requireFrom('fs', fromDir); |
425 | | - _moduleCache['fs/promises'] = fsModule.promises; |
| 427 | + __internalModuleCache['fs/promises'] = fsModule.promises; |
426 | 428 | _debugRequire('loaded', name, 'fs-promises-special'); |
427 | 429 | return fsModule.promises; |
428 | 430 | } |
429 | 431 |
|
430 | 432 | // Special handling for stream/promises module. |
431 | 433 | // Expose promise-based wrappers backed by stream callback APIs. |
432 | 434 | if (name === 'stream/promises') { |
433 | | - if (_moduleCache['stream/promises']) return _moduleCache['stream/promises']; |
| 435 | + if (__internalModuleCache['stream/promises']) return __internalModuleCache['stream/promises']; |
434 | 436 | const streamModule = _requireFrom('stream', fromDir); |
435 | 437 | const promisesModule = { |
436 | 438 | finished(stream, options) { |
|
482 | 484 | }); |
483 | 485 | }, |
484 | 486 | }; |
485 | | - _moduleCache['stream/promises'] = promisesModule; |
| 487 | + __internalModuleCache['stream/promises'] = promisesModule; |
486 | 488 | _debugRequire('loaded', name, 'stream-promises-special'); |
487 | 489 | return promisesModule; |
488 | 490 | } |
489 | 491 |
|
490 | 492 | // Special handling for child_process module |
491 | 493 | if (name === 'child_process') { |
492 | | - if (_moduleCache['child_process']) return _moduleCache['child_process']; |
493 | | - _moduleCache['child_process'] = _childProcessModule; |
| 494 | + if (__internalModuleCache['child_process']) return __internalModuleCache['child_process']; |
| 495 | + __internalModuleCache['child_process'] = _childProcessModule; |
494 | 496 | _debugRequire('loaded', name, 'child-process-special'); |
495 | 497 | return _childProcessModule; |
496 | 498 | } |
497 | 499 |
|
498 | 500 | // Special handling for http module |
499 | 501 | if (name === 'http') { |
500 | | - if (_moduleCache['http']) return _moduleCache['http']; |
501 | | - _moduleCache['http'] = _httpModule; |
| 502 | + if (__internalModuleCache['http']) return __internalModuleCache['http']; |
| 503 | + __internalModuleCache['http'] = _httpModule; |
502 | 504 | _debugRequire('loaded', name, 'http-special'); |
503 | 505 | return _httpModule; |
504 | 506 | } |
505 | 507 |
|
506 | 508 | // Special handling for https module |
507 | 509 | if (name === 'https') { |
508 | | - if (_moduleCache['https']) return _moduleCache['https']; |
509 | | - _moduleCache['https'] = _httpsModule; |
| 510 | + if (__internalModuleCache['https']) return __internalModuleCache['https']; |
| 511 | + __internalModuleCache['https'] = _httpsModule; |
510 | 512 | _debugRequire('loaded', name, 'https-special'); |
511 | 513 | return _httpsModule; |
512 | 514 | } |
513 | 515 |
|
514 | 516 | // Special handling for http2 module |
515 | 517 | if (name === 'http2') { |
516 | | - if (_moduleCache['http2']) return _moduleCache['http2']; |
517 | | - _moduleCache['http2'] = _http2Module; |
| 518 | + if (__internalModuleCache['http2']) return __internalModuleCache['http2']; |
| 519 | + __internalModuleCache['http2'] = _http2Module; |
518 | 520 | _debugRequire('loaded', name, 'http2-special'); |
519 | 521 | return _http2Module; |
520 | 522 | } |
521 | 523 |
|
522 | 524 | // Special handling for dns module |
523 | 525 | if (name === 'dns') { |
524 | | - if (_moduleCache['dns']) return _moduleCache['dns']; |
525 | | - _moduleCache['dns'] = _dnsModule; |
| 526 | + if (__internalModuleCache['dns']) return __internalModuleCache['dns']; |
| 527 | + __internalModuleCache['dns'] = _dnsModule; |
526 | 528 | _debugRequire('loaded', name, 'dns-special'); |
527 | 529 | return _dnsModule; |
528 | 530 | } |
529 | 531 |
|
530 | 532 | // Special handling for os module |
531 | 533 | if (name === 'os') { |
532 | | - if (_moduleCache['os']) return _moduleCache['os']; |
533 | | - _moduleCache['os'] = _osModule; |
| 534 | + if (__internalModuleCache['os']) return __internalModuleCache['os']; |
| 535 | + __internalModuleCache['os'] = _osModule; |
534 | 536 | _debugRequire('loaded', name, 'os-special'); |
535 | 537 | return _osModule; |
536 | 538 | } |
537 | 539 |
|
538 | 540 | // Special handling for module module |
539 | 541 | if (name === 'module') { |
540 | | - if (_moduleCache['module']) return _moduleCache['module']; |
541 | | - _moduleCache['module'] = _moduleModule; |
| 542 | + if (__internalModuleCache['module']) return __internalModuleCache['module']; |
| 543 | + __internalModuleCache['module'] = _moduleModule; |
542 | 544 | _debugRequire('loaded', name, 'module-special'); |
543 | 545 | return _moduleModule; |
544 | 546 | } |
|
553 | 555 | // Special handling for async_hooks. |
554 | 556 | // This provides the minimum API surface needed by tracing libraries. |
555 | 557 | if (name === 'async_hooks') { |
556 | | - if (_moduleCache['async_hooks']) return _moduleCache['async_hooks']; |
| 558 | + if (__internalModuleCache['async_hooks']) return __internalModuleCache['async_hooks']; |
557 | 559 |
|
558 | 560 | class AsyncLocalStorage { |
559 | 561 | constructor() { |
|
622 | 624 | executionAsyncResource() { return null; }, |
623 | 625 | }; |
624 | 626 |
|
625 | | - _moduleCache['async_hooks'] = asyncHooksModule; |
| 627 | + __internalModuleCache['async_hooks'] = asyncHooksModule; |
626 | 628 | _debugRequire('loaded', name, 'async-hooks-special'); |
627 | 629 | return asyncHooksModule; |
628 | 630 | } |
629 | 631 |
|
630 | 632 | // No-op diagnostics_channel stub — channels report no subscribers |
631 | 633 | if (name === 'diagnostics_channel') { |
632 | | - if (_moduleCache[name]) return _moduleCache[name]; |
| 634 | + if (__internalModuleCache[name]) return __internalModuleCache[name]; |
633 | 635 |
|
634 | 636 | function _createChannel() { |
635 | 637 | return { |
|
660 | 662 | }, |
661 | 663 | }; |
662 | 664 |
|
663 | | - _moduleCache[name] = dcModule; |
| 665 | + __internalModuleCache[name] = dcModule; |
664 | 666 | _debugRequire('loaded', name, 'diagnostics-channel-special'); |
665 | 667 | return dcModule; |
666 | 668 | } |
667 | 669 |
|
668 | 670 | // Get deferred module stubs |
669 | 671 | if (_deferredCoreModules.has(name)) { |
670 | | - if (_moduleCache[name]) return _moduleCache[name]; |
| 672 | + if (__internalModuleCache[name]) return __internalModuleCache[name]; |
671 | 673 | const deferredStub = _createDeferredModuleStub(name); |
672 | | - _moduleCache[name] = deferredStub; |
| 674 | + __internalModuleCache[name] = deferredStub; |
673 | 675 | _debugRequire('loaded', name, 'deferred-stub'); |
674 | 676 | return deferredStub; |
675 | 677 | } |
|
682 | 684 | // Try to load polyfill first (for built-in modules like path, events, etc.) |
683 | 685 | const polyfillCode = _loadPolyfill.applySyncPromise(undefined, [name]); |
684 | 686 | if (polyfillCode !== null) { |
685 | | - if (_moduleCache[name]) return _moduleCache[name]; |
| 687 | + if (__internalModuleCache[name]) return __internalModuleCache[name]; |
686 | 688 |
|
687 | 689 | const moduleObj = { exports: {} }; |
688 | 690 | _pendingModules[name] = moduleObj; |
|
695 | 697 | moduleObj.exports = result; |
696 | 698 | } |
697 | 699 |
|
698 | | - _moduleCache[name] = moduleObj.exports; |
| 700 | + __internalModuleCache[name] = moduleObj.exports; |
699 | 701 | delete _pendingModules[name]; |
700 | 702 | _debugRequire('loaded', name, 'polyfill'); |
701 | | - return _moduleCache[name]; |
| 703 | + return __internalModuleCache[name]; |
702 | 704 | } |
703 | 705 |
|
704 | 706 | // Resolve module path using host-side resolution |
|
708 | 710 | cacheKey = resolved; |
709 | 711 |
|
710 | 712 | // Check cache with resolved path |
711 | | - if (_moduleCache[cacheKey]) { |
| 713 | + if (__internalModuleCache[cacheKey]) { |
712 | 714 | _debugRequire('cache-hit', name, cacheKey); |
713 | | - return _moduleCache[cacheKey]; |
| 715 | + return __internalModuleCache[cacheKey]; |
714 | 716 | } |
715 | 717 |
|
716 | 718 | // Check if we're currently loading this module (circular dep) |
|
730 | 732 | // Handle JSON files |
731 | 733 | if (resolved.endsWith('.json')) { |
732 | 734 | const parsed = JSON.parse(source); |
733 | | - _moduleCache[cacheKey] = parsed; |
| 735 | + __internalModuleCache[cacheKey] = parsed; |
734 | 736 | return parsed; |
735 | 737 | } |
736 | 738 |
|
|
813 | 815 | } |
814 | 816 |
|
815 | 817 | // Cache with resolved path |
816 | | - _moduleCache[cacheKey] = module.exports; |
| 818 | + __internalModuleCache[cacheKey] = module.exports; |
817 | 819 | delete _pendingModules[cacheKey]; |
818 | 820 | _debugRequire('loaded', name, cacheKey); |
819 | 821 |
|
|
822 | 824 |
|
823 | 825 | // Expose _requireFrom globally so module polyfill can access it |
824 | 826 | __requireExposeCustomGlobal("_requireFrom", _requireFrom); |
| 827 | + |
| 828 | + // Block module cache poisoning: create a read-only Proxy over the real cache. |
| 829 | + // Internal require writes go through __internalModuleCache (captured above); |
| 830 | + // sandbox code sees only this Proxy which rejects set/delete/defineProperty. |
| 831 | + const __moduleCacheProxy = new Proxy(__internalModuleCache, { |
| 832 | + get(target, prop, receiver) { |
| 833 | + return Reflect.get(target, prop, receiver); |
| 834 | + }, |
| 835 | + set(_target, prop) { |
| 836 | + throw new TypeError("Cannot set require.cache['" + String(prop) + "']"); |
| 837 | + }, |
| 838 | + deleteProperty(_target, prop) { |
| 839 | + throw new TypeError("Cannot delete require.cache['" + String(prop) + "']"); |
| 840 | + }, |
| 841 | + defineProperty(_target, prop) { |
| 842 | + throw new TypeError("Cannot define property '" + String(prop) + "' on require.cache"); |
| 843 | + }, |
| 844 | + has(target, prop) { |
| 845 | + return Reflect.has(target, prop); |
| 846 | + }, |
| 847 | + ownKeys(target) { |
| 848 | + return Reflect.ownKeys(target); |
| 849 | + }, |
| 850 | + getOwnPropertyDescriptor(target, prop) { |
| 851 | + return Reflect.getOwnPropertyDescriptor(target, prop); |
| 852 | + }, |
| 853 | + }); |
| 854 | + |
| 855 | + // Expose read-only proxy as require.cache |
| 856 | + globalThis.require.cache = __moduleCacheProxy; |
| 857 | + |
| 858 | + // Replace _moduleCache global with read-only proxy so sandbox code |
| 859 | + // cannot bypass require.cache protection via the raw global. |
| 860 | + // Keep configurable:true — applyCustomGlobalExposurePolicy will lock it |
| 861 | + // down to non-configurable after all bridge setup completes. |
| 862 | + Object.defineProperty(globalThis, '_moduleCache', { |
| 863 | + value: __moduleCacheProxy, |
| 864 | + writable: false, |
| 865 | + configurable: true, |
| 866 | + enumerable: false, |
| 867 | + }); |
| 868 | + |
| 869 | + // Update Module._cache references to use the read-only proxy |
| 870 | + if (typeof _moduleModule !== 'undefined') { |
| 871 | + if (_moduleModule.Module) { |
| 872 | + _moduleModule.Module._cache = __moduleCacheProxy; |
| 873 | + } |
| 874 | + _moduleModule._cache = __moduleCacheProxy; |
| 875 | + } |
0 commit comments