diff --git a/lib/internal/modules/cjs/helpers.js b/lib/internal/modules/cjs/helpers.js index 3c49ab0a5d84cb..b546da0828a755 100644 --- a/lib/internal/modules/cjs/helpers.js +++ b/lib/internal/modules/cjs/helpers.js @@ -27,6 +27,10 @@ const { getOptionValue } = require('internal/options'); const { setOwnProperty } = require('internal/util'); const userConditions = getOptionValue('--conditions'); +const { + require_private_symbol, +} = internalBinding('util'); + let debug = require('internal/util/debuglog').debuglog('module', (fn) => { debug = fn; }); @@ -86,7 +90,7 @@ function makeRequireFunction(mod, redirects) { filepath = fileURLToPath(destination); urlToFileCache.set(href, filepath); } - return mod.require(filepath); + return mod[require_private_symbol](mod, filepath); } } if (missing) { @@ -96,11 +100,11 @@ function makeRequireFunction(mod, redirects) { ArrayPrototypeJoin([...conditions], ', ') )); } - return mod.require(specifier); + return mod[require_private_symbol](mod, specifier); }; } else { require = function require(path) { - return mod.require(path); + return mod[require_private_symbol](mod, path); }; } diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 1bca6706ea9b44..ea7bc8b389e9c3 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -95,6 +95,9 @@ const { sep } = path; const { internalModuleStat } = internalBinding('fs'); const packageJsonReader = require('internal/modules/package_json_reader'); const { safeGetenv } = internalBinding('credentials'); +const { + require_private_symbol, +} = internalBinding('util'); const { cjsConditions, hasEsmSyntax, @@ -155,6 +158,20 @@ let requireDepth = 0; let statCache = null; let isPreloading = false; +function internalRequire(module, id) { + validateString(id, 'id'); + if (id === '') { + throw new ERR_INVALID_ARG_VALUE('id', id, + 'must be a non-empty string'); + } + requireDepth++; + try { + return Module._load(id, module, /* isMain */ false); + } finally { + requireDepth--; + } +} + function stat(filename) { filename = path.toNamespacedPath(filename); if (statCache !== null) { @@ -203,6 +220,15 @@ function Module(id = '', parent) { this.filename = null; this.loaded = false; this.children = []; + let redirects; + if (policy?.manifest) { + const moduleURL = pathToFileURL(id); + redirects = policy.manifest.getDependencyMapper(moduleURL); + } + setOwnProperty(this, 'require', makeRequireFunction(this, redirects)); + // Loads a module at the given file path. Returns that module's + // `exports` property. + this[require_private_symbol] = internalRequire; } const builtinModules = []; @@ -863,6 +889,7 @@ Module._load = function(request, parent, isMain) { if (isMain) { process.mainModule = module; + setOwnProperty(module.require, 'main', process.mainModule); module.id = '.'; } @@ -1053,24 +1080,6 @@ Module.prototype.load = function(filename) { esmLoader.cjsCache.set(this, exports); }; - -// Loads a module at the given file path. Returns that module's -// `exports` property. -Module.prototype.require = function(id) { - validateString(id, 'id'); - if (id === '') { - throw new ERR_INVALID_ARG_VALUE('id', id, - 'must be a non-empty string'); - } - requireDepth++; - try { - return Module._load(id, this, /* isMain */ false); - } finally { - requireDepth--; - } -}; - - // Resolved path to process.argv[1] will be lazily placed here // (needed for setting breakpoint when called with --inspect-brk) let resolvedArgv; @@ -1118,10 +1127,9 @@ function wrapSafe(filename, content, cjsModuleInstance) { // Returns exception, if any. Module.prototype._compile = function(content, filename) { let moduleURL; - let redirects; if (policy?.manifest) { moduleURL = pathToFileURL(filename); - redirects = policy.manifest.getDependencyMapper(moduleURL); + policy.manifest.getDependencyMapper(moduleURL); policy.manifest.assertIntegrity(moduleURL, content); } @@ -1152,7 +1160,6 @@ Module.prototype._compile = function(content, filename) { } } const dirname = path.dirname(filename); - const require = makeRequireFunction(this, redirects); let result; const exports = this.exports; const thisValue = exports; @@ -1160,10 +1167,10 @@ Module.prototype._compile = function(content, filename) { if (requireDepth === 0) statCache = new SafeMap(); if (inspectorWrapper) { result = inspectorWrapper(compiledWrapper, thisValue, exports, - require, module, filename, dirname); + module.require, module, filename, dirname); } else { result = ReflectApply(compiledWrapper, thisValue, - [exports, require, module, filename, dirname]); + [exports, module.require, module, filename, dirname]); } hasLoadedAnyUserCJSModule = true; if (requireDepth === 0) statCache = null; @@ -1339,7 +1346,7 @@ Module._preloadModules = function(requests) { } } for (let n = 0; n < requests.length; n++) - parent.require(requests[n]); + internalRequire(parent, requests[n]); isPreloading = false; }; diff --git a/src/env.h b/src/env.h index bd36e11c637f79..056e927306e7f3 100644 --- a/src/env.h +++ b/src/env.h @@ -174,7 +174,8 @@ class NoArrayBufferZeroFillScope { V(napi_type_tag, "node:napi:type_tag") \ V(napi_wrapper, "node:napi:wrapper") \ V(untransferable_object_private_symbol, "node:untransferableObject") \ - V(exiting_aliased_Uint32Array, "node:exiting_aliased_Uint32Array") + V(exiting_aliased_Uint32Array, "node:exiting_aliased_Uint32Array") \ + V(require_private_symbol, "node:require_private_symbol") // Symbols are per-isolate primitives but Environment proxies them // for the sake of convenience. diff --git a/test/fixtures/policy-manifest/main-module-bypass.js b/test/fixtures/policy-manifest/main-module-bypass.js new file mode 100644 index 00000000000000..a1cec2ddd3ffbb --- /dev/null +++ b/test/fixtures/policy-manifest/main-module-bypass.js @@ -0,0 +1 @@ +process.mainModule.require('os').cpus(); diff --git a/test/fixtures/policy-manifest/object-define-property-bypass.js b/test/fixtures/policy-manifest/object-define-property-bypass.js new file mode 100644 index 00000000000000..5543fd35b28b26 --- /dev/null +++ b/test/fixtures/policy-manifest/object-define-property-bypass.js @@ -0,0 +1,19 @@ +let requires = new WeakMap() +Object.defineProperty(Object.getPrototypeOf(module), 'require', { + get() { + return requires.get(this); + }, + set(v) { + requires.set(this, v); + process.nextTick(() => { + let fs = Reflect.apply(v, this, ['fs']) + if (typeof fs.readFileSync === 'function') { + process.exit(1); + } + }) + return requires.get(this); + }, + configurable: true +}) + +require('./valid-module') diff --git a/test/fixtures/policy-manifest/onerror-exit.json b/test/fixtures/policy-manifest/onerror-exit.json new file mode 100644 index 00000000000000..24bd92817d24b1 --- /dev/null +++ b/test/fixtures/policy-manifest/onerror-exit.json @@ -0,0 +1,9 @@ +{ + "onerror": "exit", + "scopes": { + "file:": { + "integrity": true, + "dependencies": {} + } + } +} diff --git a/test/fixtures/policy-manifest/onerror-resource-exit.json b/test/fixtures/policy-manifest/onerror-resource-exit.json new file mode 100644 index 00000000000000..f08bc8d32c07e7 --- /dev/null +++ b/test/fixtures/policy-manifest/onerror-resource-exit.json @@ -0,0 +1,17 @@ +{ + "onerror": "exit", + "resources": { + "./object-define-property-bypass.js": { + "integrity": true, + "dependencies": { + "./valid-module": true + } + }, + "./valid-module.js": { + "integrity": true, + "dependencies": { + "fs": true + } + } + } +} diff --git a/test/fixtures/policy-manifest/valid-module.js b/test/fixtures/policy-manifest/valid-module.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/message/source_map_disabled_by_api.out b/test/message/source_map_disabled_by_api.out index d2cca7da5297e3..67e80b28c3f977 100644 --- a/test/message/source_map_disabled_by_api.out +++ b/test/message/source_map_disabled_by_api.out @@ -8,7 +8,7 @@ Error: an error! at Object.Module._extensions..js (node:internal/modules/cjs/loader:*) at Module.load (node:internal/modules/cjs/loader:*) at Function.Module._load (node:internal/modules/cjs/loader:*) - at Module.require (node:internal/modules/cjs/loader:*) + at Module.internalRequire (node:internal/modules/cjs/loader:*) *enclosing-call-site.js:16 throw new Error('an error!') ^ @@ -23,4 +23,4 @@ Error: an error! at Object.Module._extensions..js (node:internal/modules/cjs/loader:*) at Module.load (node:internal/modules/cjs/loader:*) at Function.Module._load (node:internal/modules/cjs/loader:*) - at Module.require (node:internal/modules/cjs/loader:*) + at Module.internalRequire (node:internal/modules/cjs/loader:*) diff --git a/test/message/source_map_enabled_by_api.out b/test/message/source_map_enabled_by_api.out index 525ceccec12e4b..48d79885c1579f 100644 --- a/test/message/source_map_enabled_by_api.out +++ b/test/message/source_map_enabled_by_api.out @@ -12,7 +12,7 @@ Error: an error! at Object.Module._extensions..js (node:internal/modules/cjs/loader:*) at Module.load (node:internal/modules/cjs/loader:*) at Function.Module._load (node:internal/modules/cjs/loader:*) - at Module.require (node:internal/modules/cjs/loader:*) + at Module.internalRequire (node:internal/modules/cjs/loader:*) *enclosing-call-site-min.js:1 var functionA=function(){functionB()};function functionB(){functionC()}var functionC=function(){functionD()},functionD=function(){if(0 (*source_map_reference_error_tabs.js:* at Module._compile (node:internal/modules/cjs/loader:* diff --git a/test/message/source_map_throw_catch.out b/test/message/source_map_throw_catch.out index 95bba5eee3e9dc..eb772c64d21a3e 100644 --- a/test/message/source_map_throw_catch.out +++ b/test/message/source_map_throw_catch.out @@ -9,7 +9,7 @@ Error: an exception at Object.Module._extensions..js (node:internal/modules/cjs/loader:*) at Module.load (node:internal/modules/cjs/loader:*) at Function.Module._load (node:internal/modules/cjs/loader:*) - at Module.require (node:internal/modules/cjs/loader:*) + at Module.internalRequire (node:internal/modules/cjs/loader:*) at require (node:internal/modules/cjs/helpers:*) at Object. (*source_map_throw_catch.js:6:3) at Module._compile (node:internal/modules/cjs/loader:*) diff --git a/test/message/source_map_throw_first_tick.out b/test/message/source_map_throw_first_tick.out index efa97a1d9f56dd..df045f29ffec0b 100644 --- a/test/message/source_map_throw_first_tick.out +++ b/test/message/source_map_throw_first_tick.out @@ -9,7 +9,7 @@ Error: an exception at Object.Module._extensions..js (node:internal/modules/cjs/loader:*) at Module.load (node:internal/modules/cjs/loader:*) at Function.Module._load (node:internal/modules/cjs/loader:*) - at Module.require (node:internal/modules/cjs/loader:*) + at Module.internalRequire (node:internal/modules/cjs/loader:*) at require (node:internal/modules/cjs/helpers:*) at Object. (*source_map_throw_first_tick.js:5:1) at Module._compile (node:internal/modules/cjs/loader:*) diff --git a/test/message/source_map_throw_icu.out b/test/message/source_map_throw_icu.out index 78482d73ddf037..9a667746a79b49 100644 --- a/test/message/source_map_throw_icu.out +++ b/test/message/source_map_throw_icu.out @@ -9,7 +9,7 @@ Error: an error at Object.Module._extensions..js (node:internal/modules/cjs/loader:* at Module.load (node:internal/modules/cjs/loader:* at Function.Module._load (node:internal/modules/cjs/loader:* - at Module.require (node:internal/modules/cjs/loader:* + at Module.internalRequire (node:internal/modules/cjs/loader:* at require (node:internal/modules/cjs/helpers:* at Object. (*source_map_throw_icu.js:* at Module._compile (node:internal/modules/cjs/loader:* diff --git a/test/parallel/test-policy-manifest.js b/test/parallel/test-policy-manifest.js index a8494175f3e68f..f8bebdf4cf69f1 100644 --- a/test/parallel/test-policy-manifest.js +++ b/test/parallel/test-policy-manifest.js @@ -11,15 +11,58 @@ const assert = require('assert'); const { spawnSync } = require('child_process'); const fixtures = require('../common/fixtures.js'); -const policyFilepath = fixtures.path('policy-manifest', 'invalid.json'); - -const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - './fhqwhgads.js', -]); - -assert.notStrictEqual(result.status, 0); -const stderr = result.stderr.toString(); -assert.match(stderr, /ERR_MANIFEST_INVALID_SPECIFIER/); -assert.match(stderr, /pattern needs to have a single trailing "\*"/); +{ + const policyFilepath = fixtures.path('policy-manifest', 'invalid.json'); + const result = spawnSync(process.execPath, [ + '--experimental-policy', + policyFilepath, + './fhqwhgads.js', + ]); + + assert.notStrictEqual(result.status, 0); + const stderr = result.stderr.toString(); + assert.match(stderr, /ERR_MANIFEST_INVALID_SPECIFIER/); + assert.match(stderr, /pattern needs to have a single trailing "\*"/); +} + +{ + const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); + const result = spawnSync(process.execPath, [ + '--experimental-policy', + policyFilepath, + '-e', + 'require("os").cpus()', + ]); + + assert.notStrictEqual(result.status, 0); + const stderr = result.stderr.toString(); + assert.match(stderr, /ERR_MANIFEST_DEPENDENCY_MISSING/); + assert.match(stderr, /does not list module as a dependency specifier for conditions: require, node, node-addons/); +} + +{ + const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); + const mainModuleBypass = fixtures.path('policy-manifest', 'main-module-bypass.js'); + const result = spawnSync(process.execPath, [ + '--experimental-policy', + policyFilepath, + mainModuleBypass, + ]); + + assert.notStrictEqual(result.status, 0); + const stderr = result.stderr.toString(); + assert.match(stderr, /ERR_MANIFEST_DEPENDENCY_MISSING/); + assert.match(stderr, /does not list os as a dependency specifier for conditions: require, node, node-addons/); +} + +{ + const policyFilepath = fixtures.path('policy-manifest', 'onerror-resource-exit.json'); + const objectDefinePropertyBypass = fixtures.path('policy-manifest', 'object-define-property-bypass.js'); + const result = spawnSync(process.execPath, [ + '--experimental-policy', + policyFilepath, + objectDefinePropertyBypass, + ]); + + assert.strictEqual(result.status, 0); +} diff --git a/typings/internalBinding/util.d.ts b/typings/internalBinding/util.d.ts index bb85acee21a458..4d90ac12770cda 100644 --- a/typings/internalBinding/util.d.ts +++ b/typings/internalBinding/util.d.ts @@ -18,6 +18,7 @@ declare function InternalBinding(binding: 'util'): { napi_wrapper: 6; untransferable_object_private_symbol: 7; exiting_aliased_Uint32Array: 8; + require_private_symbol: 9; kPending: 0; kFulfilled: 1;