diff --git a/benchmark/policy/policy-startup.js b/benchmark/policy/policy-startup.js deleted file mode 100644 index 9ee84fff4d0452..00000000000000 --- a/benchmark/policy/policy-startup.js +++ /dev/null @@ -1,51 +0,0 @@ -// Tests the impact on eager operations required for policies affecting -// general startup, does not test lazy operations -'use strict'; -const common = require('../common.js'); - -const configs = { - n: [1024], -}; - -const options = { - flags: ['--expose-internals'], -}; - -const bench = common.createBenchmark(main, configs, options); - -function main(conf) { - const hash = (str, algo) => { - const hash = require('crypto').createHash(algo); - return hash.update(str).digest('base64'); - }; - const resources = Object.fromEntries( - // Simulate graph of 1k modules - Array.from({ length: 1024 }, (_, i) => { - return [`./_${i}`, { - integrity: `sha256-${hash(`// ./_${i}`, 'sha256')}`, - dependencies: Object.fromEntries(Array.from({ - // Average 3 deps per 4 modules - length: Math.floor((i % 4) / 2), - }, (_, ii) => { - return [`_${ii}`, `./_${i - ii}`]; - })), - }]; - }), - ); - const json = JSON.parse(JSON.stringify({ resources }), (_, o) => { - if (o && typeof o === 'object') { - Reflect.setPrototypeOf(o, null); - Object.freeze(o); - } - return o; - }); - const { Manifest } = require('internal/policy/manifest'); - - bench.start(); - - for (let i = 0; i < conf.n; i++) { - new Manifest(json, 'file://benchmark/policy-relative'); - } - - bench.end(conf.n); -} diff --git a/doc/api/cli.md b/doc/api/cli.md index 363c42f030085b..dbfa6045567a77 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -881,16 +881,6 @@ following permissions are restricted: * Child Process - manageable through [`--allow-child-process`][] flag * Worker Threads - manageable through [`--allow-worker`][] flag -### `--experimental-policy` - - - -> Stability: 0 - Deprecated: Will be removed shortly. - -Use the specified file as a security policy. - ### `--experimental-require-module` - -> Stability: 0 - Deprecated: Will be removed shortly. - -Instructs Node.js to error prior to running any code if the policy does not have -the specified integrity. It expects a [Subresource Integrity][] string as a -parameter. - ### `--preserve-symlinks` + +An attempt was made to load a resource, but the resource did not match the +integrity defined by the policy manifest. See the documentation for policy +manifests for more information. + + + +### `ERR_MANIFEST_DEPENDENCY_MISSING` + + + +An attempt was made to load a resource, but the resource was not listed as a +dependency from the location that attempted to load it. See the documentation +for policy manifests for more information. + + + +### `ERR_MANIFEST_INTEGRITY_MISMATCH` + + + +An attempt was made to load a policy manifest, but the manifest had multiple +entries for a resource which did not match each other. Update the manifest +entries to match in order to resolve this error. See the documentation for +policy manifests for more information. + + + +### `ERR_MANIFEST_INVALID_RESOURCE_FIELD` + + + +A policy manifest resource had an invalid value for one of its fields. Update +the manifest entry to match in order to resolve this error. See the +documentation for policy manifests for more information. + + + +### `ERR_MANIFEST_INVALID_SPECIFIER` + + + +A policy manifest resource had an invalid value for one of its dependency +mappings. Update the manifest entry to match to resolve this error. See the +documentation for policy manifests for more information. + + + +### `ERR_MANIFEST_PARSE_POLICY` + + + +An attempt was made to load a policy manifest, but the manifest was unable to +be parsed. See the documentation for policy manifests for more information. + + + +### `ERR_MANIFEST_TDZ` + + + +An attempt was made to read from a policy manifest, but the manifest +initialization has not yet taken place. This is likely a bug in Node.js. + + + +### `ERR_MANIFEST_UNKNOWN_ONERROR` + + + +A policy manifest was loaded, but had an unknown value for its "onerror" +behavior. See the documentation for policy manifests for more information. + ### `ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST` @@ -4016,7 +4048,6 @@ An error occurred trying to allocate memory. This should never happen. [domains]: domain.md [event emitter-based]: events.md#class-eventemitter [file descriptors]: https://en.wikipedia.org/wiki/File_descriptor -[policy]: permissions.md#policies [relative URL]: https://url.spec.whatwg.org/#relative-url-string [self-reference a package using its name]: packages.md#self-referencing-a-package-using-its-name [special scheme]: https://url.spec.whatwg.org/#special-scheme diff --git a/doc/api/policy.md b/doc/api/policy.md deleted file mode 100644 index c3a974a2d81b2b..00000000000000 --- a/doc/api/policy.md +++ /dev/null @@ -1,11 +0,0 @@ -# Policies - - - - - -> Stability: 1 - Experimental - -The former Policies documentation is now at [Permissions documentation][]. - -[Permissions documentation]: permissions.md#policies diff --git a/doc/node.1 b/doc/node.1 index c058694ac8f7e6..fcaf971f240d72 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -174,9 +174,6 @@ Enable experimental support for loading modules using `import` over `https:`. .It Fl -experimental-permission Enable the experimental permission model. . -.It Fl -experimental-policy -Use the specified file as a security policy. -. .It Fl -experimental-shadow-realm Use this flag to enable ShadowRealm support. . @@ -334,9 +331,6 @@ Among other uses, this can be used to enable FIPS-compliant crypto if Node.js is .It Fl -pending-deprecation Emit pending deprecation warnings. . -.It Fl -policy-integrity Ns = Ns Ar sri -Instructs Node.js to error prior to running any code if the policy does not have the specified integrity. It expects a Subresource Integrity string as a parameter. -. .It Fl -preserve-symlinks Instructs the module loader to preserve symbolic links when resolving and caching modules other than the main module. . diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 530afc07f7bb8a..5b8009f802e534 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -12,7 +12,6 @@ const { AggregateError, - ArrayFrom, ArrayIsArray, ArrayPrototypeFilter, ArrayPrototypeIncludes, @@ -1555,40 +1554,6 @@ E( ' `shortCircuit: true` in the hook\'s return.', Error, ); -E('ERR_MANIFEST_ASSERT_INTEGRITY', - (moduleURL, realIntegrities) => { - let msg = `The content of "${ - moduleURL - }" does not match the expected integrity.`; - if (realIntegrities.size) { - const sri = ArrayPrototypeJoin( - ArrayFrom(realIntegrities.entries(), - ({ 0: alg, 1: dgs }) => `${alg}-${dgs}`), - ' ', - ); - msg += ` Integrities found are: ${sri}`; - } else { - msg += ' The resource was not found in the policy.'; - } - return msg; - }, Error); -E('ERR_MANIFEST_DEPENDENCY_MISSING', - 'Manifest resource %s does not list %s as a dependency specifier for ' + - 'conditions: %s', - Error); -E('ERR_MANIFEST_INTEGRITY_MISMATCH', - 'Manifest resource %s has multiple entries but integrity lists do not match', - SyntaxError); -E('ERR_MANIFEST_INVALID_RESOURCE_FIELD', - 'Manifest resource %s has invalid property value for %s', - TypeError); -E('ERR_MANIFEST_INVALID_SPECIFIER', - 'Manifest resource %s has invalid dependency mapping %s', - TypeError); -E('ERR_MANIFEST_TDZ', 'Manifest initialization has not yet run', Error); -E('ERR_MANIFEST_UNKNOWN_ONERROR', - 'Manifest specified unknown error behavior "%s".', - SyntaxError); E('ERR_METHOD_NOT_IMPLEMENTED', 'The %s method is not implemented', Error); E('ERR_MISSING_ARGS', (...args) => { diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index c0b151a1eac9de..aa329b9fe04f15 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -94,8 +94,6 @@ port.on('message', (message) => { environmentData, filename, hasStdin, - manifestSrc, - manifestURL, publicPort, workerData, } = message; @@ -130,9 +128,6 @@ port.on('message', (message) => { workerIo.sharedCwdCounter = cwdCounter; } - if (manifestSrc) { - require('internal/process/policy').setup(manifestSrc, manifestURL); - } const isLoaderWorker = doEval === 'internal' && filename === require('internal/modules/esm/utils').loaderWorkerId; diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index ab373dcb5032a2..69041d154c6fa2 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -140,11 +140,6 @@ const fs = require('fs'); const path = require('path'); const { internalModuleStat } = internalBinding('fs'); const { safeGetenv } = internalBinding('credentials'); -const { - privateSymbols: { - require_private_symbol, - }, -} = internalBinding('util'); const { getCjsConditions, initializeCjsConditions, @@ -156,9 +151,6 @@ const { } = require('internal/modules/helpers'); const packageJsonReader = require('internal/modules/package_json_reader'); const { getOptionValue, getEmbedderOptions } = require('internal/options'); -const policy = getLazy( - () => (getOptionValue('--experimental-policy') ? require('internal/process/policy') : null), -); const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES); const permission = require('internal/process/permission'); @@ -197,25 +189,6 @@ let requireDepth = 0; let isPreloading = false; let statCache = null; -/** - * Our internal implementation of `require`. - * @param {Module} module Parent module of what is being required - * @param {string} id Specifier of the child module being imported - */ -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--; - } -} - /** * Get a path's properties, using an in-memory cache to minimize lookups. * @param {string} filename Absolute path to the file @@ -294,17 +267,6 @@ function Module(id = '', parent) { this.filename = null; this.loaded = false; this.children = []; - let redirects; - const manifest = policy()?.manifest; - if (manifest) { - const moduleURL = pathToFileURL(id); - redirects = manifest.getDependencyMapper(moduleURL); - // TODO(rafaelgss): remove the necessity of this branch - setOwnProperty(this, 'require', makeRequireFunction(this, redirects)); - // eslint-disable-next-line no-proto - setOwnProperty(this.__proto__, 'require', makeRequireFunction(this, redirects)); - } - this[require_private_symbol] = internalRequire; } /** @type {Record} */ @@ -1295,7 +1257,6 @@ Module.prototype.load = function(filename) { /** * Loads a module at the given file path. Returns that module's `exports` property. - * Note: when using the experimental policy mechanism this function is overridden. * @param {string} id * @throws {ERR_INVALID_ARG_TYPE} When `id` is not a string */ @@ -1411,14 +1372,7 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache, format) { * @param {'module'|'commonjs'|undefined} format Intended format of the module. */ Module.prototype._compile = function(content, filename, format) { - let moduleURL; let redirects; - const manifest = policy()?.manifest; - if (manifest) { - moduleURL = pathToFileURL(filename); - redirects = manifest.getDependencyMapper(moduleURL); - manifest.assertIntegrity(moduleURL, content); - } let compiledWrapper; if (format !== 'module') { @@ -1572,12 +1526,6 @@ Module._extensions['.js'] = function(module, filename) { Module._extensions['.json'] = function(module, filename) { const content = fs.readFileSync(filename, 'utf8'); - const manifest = policy()?.manifest; - if (manifest) { - const moduleURL = pathToFileURL(filename); - manifest.assertIntegrity(moduleURL, content); - } - try { setOwnProperty(module, 'exports', JSONParse(stripBOM(content))); } catch (err) { @@ -1592,12 +1540,6 @@ Module._extensions['.json'] = function(module, filename) { * @param {string} filename The file path of the module */ Module._extensions['.node'] = function(module, filename) { - const manifest = policy()?.manifest; - if (manifest) { - const content = fs.readFileSync(filename); - const moduleURL = pathToFileURL(filename); - manifest.assertIntegrity(moduleURL, content); - } // Be aware this doesn't use `content` return process.dlopen(module, path.toNamespacedPath(filename)); }; @@ -1708,7 +1650,7 @@ Module._preloadModules = function(requests) { } } for (let n = 0; n < requests.length; n++) { - internalRequire(parent, requests[n]); + parent.require(requests[n]); } isPreloading = false; }; @@ -1728,7 +1670,7 @@ Module.syncBuiltinESMExports = function syncBuiltinESMExports() { ObjectDefineProperty(Module.prototype, 'constructor', { __proto__: null, get: function() { - return policy() ? undefined : Module; + return Module; }, configurable: false, enumerable: false, diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index 7b77af35a1dfeb..1ca6495c84a029 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -12,10 +12,6 @@ const { validateAttributes, emitImportAssertionWarning } = require('internal/mod const { getOptionValue } = require('internal/options'); const { readFileSync } = require('fs'); -// Do not eagerly grab .manifest, it may be in TDZ -const policy = getOptionValue('--experimental-policy') ? - require('internal/process/policy') : - null; const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); const defaultType = @@ -66,9 +62,6 @@ async function getSource(url, context) { } throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url, supportedSchemes); } - if (policy?.manifest) { - policy.manifest.assertIntegrity(href, source); - } return { __proto__: null, responseURL, source }; } @@ -94,9 +87,6 @@ function getSourceSync(url, context) { const supportedSchemes = ['file', 'data']; throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url, supportedSchemes); } - if (policy?.manifest) { - policy.manifest.assertIntegrity(url, source); - } return { __proto__: null, responseURL, source }; } diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index bbbaed87479289..99d91ed794ea4c 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -27,9 +27,6 @@ const { BuiltinModule } = require('internal/bootstrap/realm'); const { realpathSync } = require('fs'); const { getOptionValue } = require('internal/options'); // Do not eagerly grab .manifest, it may be in TDZ -const policy = getOptionValue('--experimental-policy') ? - require('internal/process/policy') : - null; const { sep, posix: { relative: relativePosixPath }, toNamespacedPath, resolve } = require('path'); const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); @@ -46,7 +43,6 @@ const { ERR_INVALID_MODULE_SPECIFIER, ERR_INVALID_PACKAGE_CONFIG, ERR_INVALID_PACKAGE_TARGET, - ERR_MANIFEST_DEPENDENCY_MISSING, ERR_MODULE_NOT_FOUND, ERR_PACKAGE_IMPORT_NOT_DEFINED, ERR_PACKAGE_PATH_NOT_EXPORTED, @@ -1045,8 +1041,7 @@ function throwIfInvalidParentURL(parentURL) { /** * Resolves the given specifier using the provided context, which includes the parent URL and conditions. - * Throws an error if the parent URL is invalid or if the resolution is disallowed by the policy manifest. - * Otherwise, attempts to resolve the specifier and returns the resulting URL and format. + * Attempts to resolve the specifier and returns the resulting URL and format. * @param {string} specifier - The specifier to resolve. * @param {object} [context={}] - The context object containing the parent URL and conditions. * @param {string} [context.parentURL] - The URL of the parent module. @@ -1055,30 +1050,6 @@ function throwIfInvalidParentURL(parentURL) { function defaultResolve(specifier, context = {}) { let { parentURL, conditions } = context; throwIfInvalidParentURL(parentURL); - if (parentURL && policy?.manifest) { - const redirects = policy.manifest.getDependencyMapper(parentURL); - if (redirects) { - const { resolve, reaction } = redirects; - const destination = resolve(specifier, new SafeSet(conditions)); - let missing = true; - if (destination === true) { - missing = false; - } else if (destination) { - const href = destination.href; - return { __proto__: null, url: href }; - } - if (missing) { - // Prevent network requests from firing if resolution would be banned. - // Network requests can extract data by doing things like putting - // secrets in query params - reaction(new ERR_MANIFEST_DEPENDENCY_MISSING( - parentURL, - specifier, - ArrayPrototypeJoin([...conditions], ', ')), - ); - } - } - } let parsedParentURL; if (parentURL) { @@ -1207,22 +1178,3 @@ module.exports = { const { defaultGetFormatWithoutErrors, } = require('internal/modules/esm/get_format'); - -if (policy) { - const $defaultResolve = defaultResolve; - module.exports.defaultResolve = function defaultResolve( - specifier, - context, - ) { - const ret = $defaultResolve(specifier, context); - // This is a preflight check to avoid data exfiltration by query params etc. - policy.manifest.mightAllow(ret.url, () => - new ERR_MANIFEST_DEPENDENCY_MISSING( - context.parentURL, - specifier, - context.conditions, - ), - ); - return ret; - }; -} diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index f7ad4cd1be6c8d..ba13aa64bfaa24 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -2,7 +2,6 @@ const { ArrayPrototypeForEach, - ArrayPrototypeJoin, ObjectDefineProperty, ObjectPrototypeHasOwnProperty, SafeMap, @@ -14,8 +13,6 @@ const { } = primordials; const { ERR_INVALID_ARG_TYPE, - ERR_MANIFEST_DEPENDENCY_MISSING, - ERR_UNKNOWN_BUILTIN_MODULE, } = require('internal/errors').codes; const { BuiltinModule } = require('internal/bootstrap/realm'); @@ -30,11 +27,6 @@ const { getOptionValue } = require('internal/options'); const { setOwnProperty } = require('internal/util'); const { inspect } = require('internal/util/inspect'); -const { - privateSymbols: { - require_private_symbol, - }, -} = internalBinding('util'); const { canParse: URLCanParse } = internalBinding('url'); let debug = require('internal/util/debuglog').debuglog('module', (fn) => { @@ -115,69 +107,20 @@ function lazyModule() { return $Module; } -/** - * Invoke with `makeRequireFunction(module)` where `module` is the `Module` object to use as the context for the - * `require()` function. - * Use redirects to set up a mapping from a policy and restrict dependencies. - */ -const urlToFileCache = new SafeMap(); /** * Create the module-scoped `require` function to pass into CommonJS modules. * @param {Module} mod - The module to create the `require` function for. - * @param {ReturnType} redirects * @typedef {(specifier: string) => unknown} RequireFunction */ -function makeRequireFunction(mod, redirects) { +function makeRequireFunction(mod) { // lazy due to cycle const Module = lazyModule(); if (mod instanceof Module !== true) { throw new ERR_INVALID_ARG_TYPE('mod', 'Module', mod); } - /** @type {RequireFunction} */ - let require; - if (redirects) { - const id = mod.filename || mod.id; - const conditions = getCjsConditions(); - const { resolve, reaction } = redirects; - require = function require(specifier) { - let missing = true; - const destination = resolve(specifier, conditions); - if (destination === true) { - missing = false; - } else if (destination) { - const { href, protocol } = destination; - if (protocol === 'node:') { - const specifier = destination.pathname; - - if (BuiltinModule.canBeRequiredByUsers(specifier)) { - const mod = loadBuiltinModule(specifier, href); - return mod.exports; - } - throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier); - } else if (protocol === 'file:') { - let filepath = urlToFileCache.get(href); - if (!filepath) { - filepath = fileURLToPath(destination); - urlToFileCache.set(href, filepath); - } - return mod[require_private_symbol](mod, filepath); - } - } - if (missing) { - reaction(new ERR_MANIFEST_DEPENDENCY_MISSING( - id, - specifier, - ArrayPrototypeJoin([...conditions], ', '), - )); - } - return mod[require_private_symbol](mod, specifier); - }; - } else { - require = function require(path) { - // When no policy manifest, the original prototype.require is sustained - return mod.require(path); - }; + function require(path) { + return mod.require(path); } /** diff --git a/lib/internal/modules/package_json_reader.js b/lib/internal/modules/package_json_reader.js index 3df2b7bc6a05b3..9a9dcebb799c00 100644 --- a/lib/internal/modules/package_json_reader.js +++ b/lib/internal/modules/package_json_reader.js @@ -10,34 +10,13 @@ const { const modulesBinding = internalBinding('modules'); const { resolve, sep } = require('path'); const { kEmptyObject } = require('internal/util'); -const { pathToFileURL } = require('internal/url'); - -let manifest; - -/** - * @param {string} jsonPath - * @param {string} value The integrity value to check against. - */ -function checkPackageJSONIntegrity(jsonPath, value) { - if (manifest === undefined) { - const { getOptionValue } = require('internal/options'); - manifest = getOptionValue('--experimental-policy') ? - require('internal/process/policy').manifest : - null; - } - if (manifest !== null) { - const jsonURL = pathToFileURL(jsonPath); - manifest.assertIntegrity(jsonURL, value); - } -} /** * @param {string} path * @param {import('typings/internalBinding/modules').SerializedPackageConfig} contents - * @param {boolean} [checkIntegrity=false] Whether to check the integrity of the package.json file. * @returns {import('typings/internalBinding/modules').PackageConfig} */ -function deserializePackageJSON(path, contents, checkIntegrity = false) { +function deserializePackageJSON(path, contents) { if (contents === undefined) { return { __proto__: null, @@ -54,8 +33,7 @@ function deserializePackageJSON(path, contents, checkIntegrity = false) { 2: type, 3: plainImports, 4: plainExports, - 5: manifest, - 6: optionalFilePath, + 5: optionalFilePath, } = contents; // This is required to be used in getPackageScopeConfig. @@ -63,11 +41,6 @@ function deserializePackageJSON(path, contents, checkIntegrity = false) { pjsonPath = optionalFilePath; } - if (checkIntegrity) { - // parsed[5] is only available when experimental policy is enabled. - checkPackageJSONIntegrity(pjsonPath, manifest); - } - // The imports and exports fields can be either undefined or a string. // - If it's a string, it's either plain string or a stringified JSON string. // - If it's a stringified JSON string, it starts with either '[' or '{'. @@ -114,7 +87,7 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) { specifier == null ? undefined : `${specifier}`, ); - return deserializePackageJSON(jsonPath, parsed, true /* checkIntegrity */); + return deserializePackageJSON(jsonPath, parsed); } /** @@ -141,7 +114,7 @@ function getNearestParentPackageJSON(checkPath) { return undefined; } - const data = deserializePackageJSON(checkPath, result, true /* checkIntegrity */); + const data = deserializePackageJSON(checkPath, result); // Path should be the root folder of the matched package.json // For example for ~/path/package.json, it should be ~/path @@ -159,7 +132,7 @@ function getPackageScopeConfig(resolved) { const result = modulesBinding.getPackageScopeConfig(`${resolved}`); if (ArrayIsArray(result)) { - return deserializePackageJSON(`${resolved}`, result, false /* checkIntegrity */); + return deserializePackageJSON(`${resolved}`, result); } // This means that the response is a string @@ -182,7 +155,6 @@ function getPackageType(url) { } module.exports = { - checkPackageJSONIntegrity, read, readPackage, getNearestParentPackageJSON, diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index b68825950a8f75..7b26b67bfd4cd6 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -7,7 +7,6 @@ const { const { getNearestParentPackageJSONType } = internalBinding('modules'); const { getOptionValue } = require('internal/options'); -const { checkPackageJSONIntegrity } = require('internal/modules/package_json_reader'); const path = require('path'); const { pathToFileURL } = require('internal/url'); const { kEmptyObject, getCWDURL } = require('internal/util'); @@ -82,22 +81,13 @@ function shouldUseESMLoader(mainPath) { if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; } if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) { return false; } - const response = getNearestParentPackageJSONType(mainPath); + const type = getNearestParentPackageJSONType(mainPath); // No package.json or no `type` field. - if (response === undefined || response[0] === 'none') { + if (type === undefined || type === 'none') { return false; } - // TODO(@anonrig): Do not return filePath and rawContent if experimental-policy is not used. - const { - 0: type, - 1: filePath, - 2: rawContent, - } = response; - - checkPackageJSONIntegrity(filePath, rawContent); - return type === 'module'; } diff --git a/lib/internal/policy/manifest.js b/lib/internal/policy/manifest.js deleted file mode 100644 index 61d32d1185d7e7..00000000000000 --- a/lib/internal/policy/manifest.js +++ /dev/null @@ -1,751 +0,0 @@ -'use strict'; - -// #region imports -const { - ArrayIsArray, - ArrayPrototypeSort, - ObjectEntries, - ObjectFreeze, - ObjectKeys, - ObjectSetPrototypeOf, - RegExpPrototypeExec, - RegExpPrototypeSymbolReplace, - SafeMap, - SafeSet, - StringPrototypeEndsWith, - StringPrototypeStartsWith, - Symbol, -} = primordials; -const { - ERR_MANIFEST_ASSERT_INTEGRITY, - ERR_MANIFEST_INVALID_RESOURCE_FIELD, - ERR_MANIFEST_INVALID_SPECIFIER, - ERR_MANIFEST_UNKNOWN_ONERROR, -} = require('internal/errors').codes; -let debug = require('internal/util/debuglog').debuglog('policy', (fn) => { - debug = fn; -}); -const SRI = require('internal/policy/sri'); -const { URL } = require('internal/url'); -const { internalVerifyIntegrity } = internalBinding('crypto'); -const kRelativeURLStringPattern = /^\.{0,2}\//; -const { getOptionValue } = require('internal/options'); -const shouldAbortOnUncaughtException = getOptionValue( - '--abort-on-uncaught-exception', -); -const { exitCodes: { kGenericUserError } } = internalBinding('errors'); - -const { abort, exit, _rawDebug } = process; -// #endregion - -// #region constants -// From https://url.spec.whatwg.org/#special-scheme -const kSpecialSchemes = new SafeSet([ - 'file:', - 'ftp:', - 'http:', - 'https:', - 'ws:', - 'wss:', -]); - -/** - * @type {symbol} - */ -const kCascade = Symbol('cascade'); -/** - * @type {symbol} - */ -const kFallThrough = Symbol('fall through'); - -function REACTION_THROW(error) { - throw error; -} - -function REACTION_EXIT(error) { - REACTION_LOG(error); - if (shouldAbortOnUncaughtException) { - abort(); - } - exit(kGenericUserError); -} - -function REACTION_LOG(error) { - _rawDebug(error.stack); -} - -// #endregion - -// #region DependencyMapperInstance -class DependencyMapperInstance { - /** - * @type {string} - */ - href; - /** - * @type {DependencyMap | undefined} - */ - #dependencies; - /** - * @type {PatternDependencyMap | undefined} - */ - #patternDependencies; - /** - * @type {DependencyMapperInstance | null | undefined} - */ - #parentDependencyMapper; - /** - * @type {boolean} - */ - #normalized = false; - /** - * @type {boolean} - */ - cascade; - /** - * @type {boolean} - */ - allowSameHREFScope; - /** - * @param {string} parentHREF - * @param {DependencyMap | undefined} dependencies - * @param {boolean} cascade - * @param {boolean} allowSameHREFScope - */ - constructor( - parentHREF, - dependencies, - cascade = false, - allowSameHREFScope = false) { - this.href = parentHREF; - if (dependencies === kFallThrough || - dependencies === undefined || - dependencies === null) { - this.#dependencies = dependencies; - this.#patternDependencies = undefined; - } else { - const patterns = []; - const keys = ObjectKeys(dependencies); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (StringPrototypeEndsWith(key, '*')) { - const target = RegExpPrototypeExec(/^([^*]*)\*([^*]*)$/); - if (!target) { - throw new ERR_MANIFEST_INVALID_SPECIFIER( - this.href, - `${target}, pattern needs to have a single trailing "*" in target`, - ); - } - const prefix = target[1]; - const suffix = target[2]; - patterns.push([ - target.slice(0, -1), - [prefix, suffix], - ]); - } - } - ArrayPrototypeSort(patterns, (a, b) => { - return a[0] < b[0] ? -1 : 1; - }); - this.#dependencies = dependencies; - this.#patternDependencies = patterns; - } - this.cascade = cascade; - this.allowSameHREFScope = allowSameHREFScope; - ObjectFreeze(this); - } - /** - * - * @param {string} normalizedSpecifier - * @param {Set} conditions - * @param {Manifest} manifest - * @returns {URL | typeof kFallThrough | null} - */ - _resolveAlreadyNormalized(normalizedSpecifier, conditions, manifest) { - let dependencies = this.#dependencies; - debug(this.href, 'resolving', normalizedSpecifier); - if (dependencies === kFallThrough) return true; - if (dependencies !== undefined && typeof dependencies === 'object') { - const normalized = this.#normalized; - if (normalized !== true) { - /** - * @type {Record} - */ - const normalizedDependencyMap = { __proto__: null }; - for (let specifier in dependencies) { - const target = dependencies[specifier]; - specifier = canonicalizeSpecifier(specifier, manifest.href); - normalizedDependencyMap[specifier] = target; - } - ObjectFreeze(normalizedDependencyMap); - dependencies = normalizedDependencyMap; - this.#dependencies = normalizedDependencyMap; - this.#normalized = true; - } - debug(dependencies); - if (normalizedSpecifier in dependencies === true) { - const to = searchDependencies( - this.href, - dependencies[normalizedSpecifier], - conditions, - ); - debug({ to }); - if (to === true) { - return true; - } - let ret; - if (parsedURLs && parsedURLs.has(to)) { - ret = parsedURLs.get(to); - } else if (RegExpPrototypeExec(kRelativeURLStringPattern, to) !== null) { - ret = resolve(to, manifest.href); - } else { - ret = resolve(to); - } - return ret; - } - } - const { cascade } = this; - if (cascade !== true) { - return null; - } - let parentDependencyMapper = this.#parentDependencyMapper; - if (parentDependencyMapper === undefined) { - parentDependencyMapper = manifest.getScopeDependencyMapper( - this.href, - this.allowSameHREFScope, - ); - this.#parentDependencyMapper = parentDependencyMapper; - } - if (parentDependencyMapper === null) { - return null; - } - return parentDependencyMapper._resolveAlreadyNormalized( - normalizedSpecifier, - conditions, - manifest, - ); - } -} - -const kArbitraryDependencies = new DependencyMapperInstance( - 'arbitrary dependencies', - kFallThrough, - false, - true, -); -const kNoDependencies = new DependencyMapperInstance( - 'no dependencies', - null, - false, - true, -); -/** - * @param {string} href - * @param {JSONDependencyMap} dependencies - * @param {boolean} cascade - * @param {boolean} allowSameHREFScope - * @param {Map} store - */ -const insertDependencyMap = ( - href, - dependencies, - cascade, - allowSameHREFScope, - store, -) => { - if (cascade !== undefined && typeof cascade !== 'boolean') { - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'cascade'); - } - if (dependencies === true) { - store.set(href, kArbitraryDependencies); - return; - } - if (dependencies === null || dependencies === undefined) { - store.set( - href, - cascade ? - new DependencyMapperInstance(href, null, true, allowSameHREFScope) : - kNoDependencies, - ); - return; - } - if (objectButNotArray(dependencies)) { - store.set( - href, - new DependencyMapperInstance( - href, - dependencies, - cascade, - allowSameHREFScope, - ), - ); - return; - } - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'dependencies'); -}; -/** - * Finds the longest key within `this.#scopeDependencies` that covers a - * specific HREF - * @param {string} href - * @param {ScopeStore} scopeStore - * @returns {null | string} - */ -function findScopeHREF(href, scopeStore, allowSame) { - let protocol; - if (href !== '') { - // default URL parser does some stuff to special urls... skip if this is - // just the protocol - if (RegExpPrototypeExec(/^[^:]*[:]$/, href) !== null) { - protocol = href; - } else { - let currentURL = new URL(href); - const normalizedHREF = currentURL.href; - protocol = currentURL.protocol; - // Non-opaque blobs adopt origins - if (protocol === 'blob:' && currentURL.origin !== 'null') { - currentURL = new URL(currentURL.origin); - protocol = currentURL.protocol; - } - // Only a few schemes are hierarchical - if (kSpecialSchemes.has(currentURL.protocol)) { - // Make first '..' act like '.' - if (!StringPrototypeEndsWith(currentURL.pathname, '/')) { - currentURL.pathname += '/'; - } - let lastHREF; - let currentHREF = currentURL.href; - do { - if (scopeStore.has(currentHREF)) { - if (allowSame || currentHREF !== normalizedHREF) { - return currentHREF; - } - } - lastHREF = currentHREF; - currentURL = new URL('..', currentURL); - currentHREF = currentURL.href; - } while (lastHREF !== currentHREF); - } - } - } - if (scopeStore.has(protocol)) { - if (allowSame || protocol !== href) return protocol; - } - if (scopeStore.has('')) { - if (allowSame || '' !== href) return ''; - } - return null; -} -// #endregion - -/** - * @typedef {Record | typeof kFallThrough} DependencyMap - * @typedef {Array<[string, [string, string]]>} PatternDependencyMap - * @typedef {Record | null | true} JSONDependencyMap - */ -/** - * @typedef {Map} ScopeStore - * @typedef {(specifier: string) => true | URL} DependencyMapper - * @typedef {boolean | string | SRI[] | typeof kCascade} Integrity - */ - -class Manifest { - #defaultDependencies; - /** - * @type {string} - */ - href; - /** - * @type {(err: Error) => void} - * - * Performs default action for what happens when a manifest encounters - * a violation such as abort()ing or exiting the process, throwing the error, - * or logging the error. - */ - #reaction; - /** - * @type {Map} - * - * Used to find where a dependency is located. - * - * This stores functions to lazily calculate locations as needed. - * `true` is used to signify that the location is not specified - * by the manifest and default resolution should be allowed. - * - * The functions return `null` to signify that a dependency is - * not found - */ - #resourceDependencies = new SafeMap(); - /** - * @type {Map} - * - * Used to compare a resource to the content body at the resource. - * `true` is used to signify that all integrities are allowed, otherwise, - * SRI strings are parsed to compare with the body. - * - * This stores strings instead of eagerly parsing SRI strings - * and only converts them to SRI data structures when needed. - * This avoids needing to parse all SRI strings at startup even - * if some never end up being used. - */ - #resourceIntegrities = new SafeMap(); - /** - * @type {ScopeStore} - * - * Used to compare a resource to the content body at the resource. - * `true` is used to signify that all integrities are allowed, otherwise, - * SRI strings are parsed to compare with the body. - * - * Separate from #resourceDependencies due to conflicts with things like - * `blob:` being both a scope and a resource potentially as well as - * `file:` being parsed to `file:///` instead of remaining host neutral. - */ - #scopeDependencies = new SafeMap(); - /** - * @type {Map} - * - * Used to allow arbitrary loading within a scope - */ - #scopeIntegrities = new SafeMap(); - /** - * `obj` should match the policy file format described in the docs - * it is expected to not have prototype pollution issues either by reassigning - * the prototype to `null` for values or by running prior to any user code. - * - * `manifestURL` is a URL to resolve relative locations against. - * @param {object} obj - * @param {string} manifestHREF - */ - constructor(obj, manifestHREF) { - this.href = manifestHREF; - const scopes = this.#scopeDependencies; - const integrities = this.#resourceIntegrities; - const resourceDependencies = this.#resourceDependencies; - let reaction = REACTION_THROW; - - if (objectButNotArray(obj) && 'onerror' in obj) { - const behavior = obj.onerror; - if (behavior === 'exit') { - reaction = REACTION_EXIT; - } else if (behavior === 'log') { - reaction = REACTION_LOG; - } else if (behavior !== 'throw') { - throw new ERR_MANIFEST_UNKNOWN_ONERROR(behavior); - } - } - - this.#reaction = reaction; - const jsonResourcesEntries = ObjectEntries( - obj.resources ?? { __proto__: null }, - ); - const jsonScopesEntries = ObjectEntries(obj.scopes ?? { __proto__: null }); - const defaultDependencies = obj.dependencies ?? { __proto__: null }; - - this.#defaultDependencies = new DependencyMapperInstance( - 'default', - defaultDependencies === true ? kFallThrough : defaultDependencies, - false, - ); - - for (let i = 0; i < jsonResourcesEntries.length; i++) { - const { 0: originalHREF, 1: descriptor } = jsonResourcesEntries[i]; - const { cascade, dependencies, integrity } = descriptor; - const href = resolve(originalHREF, manifestHREF).href; - - if (typeof integrity !== 'undefined') { - debug('Manifest contains integrity for resource %s', originalHREF); - if (typeof integrity === 'string') { - integrities.set(href, integrity); - } else if (integrity === true) { - integrities.set(href, true); - } else { - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'integrity'); - } - } else { - integrities.set(href, cascade === true ? kCascade : false); - } - insertDependencyMap( - href, - dependencies, - cascade, - true, - resourceDependencies, - ); - } - - const scopeIntegrities = this.#scopeIntegrities; - for (let i = 0; i < jsonScopesEntries.length; i++) { - const { 0: originalHREF, 1: descriptor } = jsonScopesEntries[i]; - const { cascade, dependencies, integrity } = descriptor; - const href = emptyOrProtocolOrResolve(originalHREF, manifestHREF); - if (typeof integrity !== 'undefined') { - debug('Manifest contains integrity for scope %s', originalHREF); - if (integrity === true) { - scopeIntegrities.set(href, true); - } else { - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'integrity'); - } - } else { - scopeIntegrities.set(href, cascade === true ? kCascade : false); - } - insertDependencyMap(href, dependencies, cascade, false, scopes); - } - - ObjectFreeze(this); - } - - /** - * @param {string} requester - * @returns {{resolve: any, reaction: (err: any) => void}} - */ - getDependencyMapper(requester) { - const requesterHREF = `${requester}`; - const dependencies = this.#resourceDependencies; - /** - * @type {DependencyMapperInstance} - */ - const instance = ( - dependencies.has(requesterHREF) ? - dependencies.get(requesterHREF) ?? null : - this.getScopeDependencyMapper(requesterHREF, true) - ) ?? this.#defaultDependencies; - return { - resolve: (specifier, conditions) => { - const normalizedSpecifier = canonicalizeSpecifier( - specifier, - requesterHREF, - ); - const result = instance._resolveAlreadyNormalized( - normalizedSpecifier, - conditions, - this, - ); - if (result === kFallThrough) return true; - return result; - }, - reaction: this.#reaction, - }; - } - - mightAllow(url, onreact) { - const href = `${url}`; - debug('Checking for entry of %s', href); - if (StringPrototypeStartsWith(href, 'node:')) { - return true; - } - if (this.#resourceIntegrities.has(href)) { - return true; - } - let scope = findScopeHREF(href, this.#scopeIntegrities, true); - while (scope !== null) { - if (this.#scopeIntegrities.has(scope)) { - const entry = this.#scopeIntegrities.get(scope); - if (entry === true) { - return true; - } else if (entry !== kCascade) { - break; - } - } - const nextScope = findScopeHREF( - new URL('..', scope), - this.#scopeIntegrities, - false, - ); - if (!nextScope || nextScope === scope) { - break; - } - scope = nextScope; - } - if (onreact) { - this.#reaction(onreact()); - } - return false; - } - - assertIntegrity(url, content) { - const href = `${url}`; - debug('Checking integrity of %s', href); - const realIntegrities = new SafeMap(); - const integrities = this.#resourceIntegrities; - function processEntry(href) { - let integrityEntries = integrities.get(href); - if (integrityEntries === true) return true; - if (typeof integrityEntries === 'string') { - const sri = ObjectFreeze(SRI.parse(integrityEntries)); - integrities.set(href, sri); - integrityEntries = sri; - } - return integrityEntries; - } - if (integrities.has(href)) { - const integrityEntries = processEntry(href); - if (integrityEntries === true) return true; - if (ArrayIsArray(integrityEntries)) { - // Avoid clobbered Symbol.iterator - for (let i = 0; i < integrityEntries.length; i++) { - const { algorithm, value: expected } = integrityEntries[i]; - // TODO(tniessen): the content should not be passed as a string in the - // first place, see https://github.com/nodejs/node/issues/39707 - const mismatchedIntegrity = internalVerifyIntegrity(algorithm, content, expected); - if (mismatchedIntegrity === undefined) { - return true; - } - realIntegrities.set(algorithm, mismatchedIntegrity); - } - } - - if (integrityEntries !== kCascade) { - const error = new ERR_MANIFEST_ASSERT_INTEGRITY(url, realIntegrities); - this.#reaction(error); - } - } - let scope = findScopeHREF(href, this.#scopeIntegrities, true); - while (scope !== null) { - if (this.#scopeIntegrities.has(scope)) { - const entry = this.#scopeIntegrities.get(scope); - if (entry === true) { - return true; - } else if (entry !== kCascade) { - break; - } - } - const nextScope = findScopeHREF(scope, this.#scopeDependencies, false); - if (!nextScope) { - break; - } - scope = nextScope; - } - const error = new ERR_MANIFEST_ASSERT_INTEGRITY(url, realIntegrities); - this.#reaction(error); - } - /** - * @param {string} href - * @param {boolean} allowSameHREFScope - * @returns {DependencyMapperInstance | null} - */ - getScopeDependencyMapper(href, allowSameHREFScope) { - if (href === null) { - return this.#defaultDependencies; - } - /** @type {string | null} */ - const scopeHREF = findScopeHREF( - href, - this.#scopeDependencies, - allowSameHREFScope, - ); - if (scopeHREF === null) return this.#defaultDependencies; - return this.#scopeDependencies.get(scopeHREF); - } -} - -// Lock everything down to avoid problems even if reference is leaked somehow -ObjectSetPrototypeOf(Manifest, null); -ObjectSetPrototypeOf(Manifest.prototype, null); -ObjectFreeze(Manifest); -ObjectFreeze(Manifest.prototype); -module.exports = ObjectFreeze({ Manifest }); - -// #region URL utils - -/** - * Attempts to canonicalize relative URL strings against a base URL string - * Does not perform I/O - * If not able to canonicalize, returns the original specifier - * - * This effectively removes the possibility of the return value being a relative - * URL string - * @param {string} specifier - * @param {string} base - * @returns {string} - */ -function canonicalizeSpecifier(specifier, base) { - try { - if (RegExpPrototypeExec(kRelativeURLStringPattern, specifier) !== null) { - return resolve(specifier, base).href; - } - return resolve(specifier).href; - } catch { - // Continue regardless of error. - } - return specifier; -} - -/** - * Does a special allowance for scopes to be non-valid URLs - * that are only protocol strings or the empty string - * @param {string} resourceHREF - * @param {string} [base] - * @returns {string} - */ -const emptyOrProtocolOrResolve = (resourceHREF, base) => { - if (resourceHREF === '') return ''; - if (StringPrototypeEndsWith(resourceHREF, ':')) { - // URL parse will trim these anyway, save the compute - resourceHREF = RegExpPrototypeSymbolReplace( - // eslint-disable-next-line - /^[\x00-\x1F\x20]|\x09\x0A\x0D|[\x00-\x1F\x20]$/g, - resourceHREF, - '', - ); - if (RegExpPrototypeExec(/^[a-zA-Z][a-zA-Z+\-.]*:$/, resourceHREF) !== null) { - return resourceHREF; - } - } - return resolve(resourceHREF, base).href; -}; - -/** - * @type {Map} - */ -let parsedURLs; -/** - * Resolves a valid url string and uses the parsed cache to avoid double parsing - * costs. - * @param {string} originalHREF - * @param {string} [base] - * @returns {Readonly} - */ -const resolve = (originalHREF, base) => { - parsedURLs = parsedURLs ?? new SafeMap(); - if (parsedURLs.has(originalHREF)) { - return parsedURLs.get(originalHREF); - } else if (RegExpPrototypeExec(kRelativeURLStringPattern, originalHREF) !== null) { - const resourceURL = new URL(originalHREF, base); - parsedURLs.set(resourceURL.href, resourceURL); - return resourceURL; - } - const resourceURL = new URL(originalHREF); - parsedURLs.set(originalHREF, resourceURL); - return resourceURL; -}; - -// #endregion - -/** - * @param {any} o - * @returns {o is object} - */ -function objectButNotArray(o) { - return o && typeof o === 'object' && !ArrayIsArray(o); -} - -function searchDependencies(href, target, conditions) { - if (objectButNotArray(target)) { - const keys = ObjectKeys(target); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (conditions.has(key)) { - const ret = searchDependencies(href, target[key], conditions); - if (ret != null) { - return ret; - } - } - } - } else if (typeof target === 'string') { - return target; - } else if (target === true) { - return target; - } else { - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'dependencies'); - } - return null; -} diff --git a/lib/internal/policy/sri.js b/lib/internal/policy/sri.js deleted file mode 100644 index 675226a3a0723a..00000000000000 --- a/lib/internal/policy/sri.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; -// Utility to parse the value of -// https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute - -const { - ArrayPrototype, - ObjectDefineProperty, - ObjectFreeze, - ObjectSeal, - ObjectSetPrototypeOf, - RegExp, - RegExpPrototypeExec, - StringPrototypeSlice, -} = primordials; - -const { - ERR_SRI_PARSE, -} = require('internal/errors').codes; -const kWSP = '[\\x20\\x09]'; -const kVCHAR = '[\\x21-\\x7E]'; -const kHASH_ALGO = 'sha(?:256|384|512)'; -// Base64 -const kHASH_VALUE = '[A-Za-z0-9+/]+[=]{0,2}'; -const kHASH_EXPRESSION = `(${kHASH_ALGO})-(${kHASH_VALUE})`; -// Ungrouped since unused -const kOPTION_EXPRESSION = `(?:${kVCHAR}*)`; -const kHASH_WITH_OPTIONS = `${kHASH_EXPRESSION}(?:[?](${kOPTION_EXPRESSION}))?`; -const kSRIPattern = RegExp(`(${kWSP}*)(?:${kHASH_WITH_OPTIONS})`, 'g'); -ObjectSeal(kSRIPattern); -const kAllWSP = RegExp(`^${kWSP}*$`); -ObjectSeal(kAllWSP); - -const BufferFrom = require('buffer').Buffer.from; - -// Returns {algorithm, value (in base64 string), options,}[] -const parse = (str) => { - let prevIndex = 0; - let match; - const entries = []; - while ((match = RegExpPrototypeExec(kSRIPattern, str)) !== null) { - if (match.index !== prevIndex) { - throw new ERR_SRI_PARSE(str, str[prevIndex], prevIndex); - } - if (entries.length > 0 && match[1] === '') { - throw new ERR_SRI_PARSE(str, str[prevIndex], prevIndex); - } - - // Avoid setters being fired - ObjectDefineProperty(entries, entries.length, { - __proto__: null, - enumerable: true, - configurable: true, - value: ObjectFreeze({ - __proto__: null, - algorithm: match[2], - value: BufferFrom(match[3], 'base64'), - options: match[4] === undefined ? null : match[4], - }), - }); - prevIndex += match[0].length; - } - - if (prevIndex !== str.length) { - if (RegExpPrototypeExec(kAllWSP, StringPrototypeSlice(str, prevIndex)) === null) { - throw new ERR_SRI_PARSE(str, str[prevIndex], prevIndex); - } - } - return ObjectSetPrototypeOf(entries, ArrayPrototype); -}; - -module.exports = { - parse, -}; diff --git a/lib/internal/process/policy.js b/lib/internal/process/policy.js deleted file mode 100644 index 8e07cb92118c84..00000000000000 --- a/lib/internal/process/policy.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -const { - JSONParse, - ObjectFreeze, - ReflectSetPrototypeOf, -} = primordials; - -const { - ERR_ACCESS_DENIED, - ERR_MANIFEST_TDZ, -} = require('internal/errors').codes; -const { Manifest } = require('internal/policy/manifest'); -let manifest; -let manifestSrc; -let manifestURL; - -module.exports = ObjectFreeze({ - __proto__: null, - setup(src, url) { - manifestSrc = src; - manifestURL = url; - if (src === null) { - manifest = null; - return; - } - - const json = JSONParse(src, (_, o) => { - if (o && typeof o === 'object') { - ReflectSetPrototypeOf(o, null); - ObjectFreeze(o); - } - return o; - }); - manifest = new Manifest(json, url); - - // process.binding() is deprecated (DEP0111) and trivially allows bypassing - // policies, so if policies are enabled, make this API unavailable. - process.binding = function binding(_module) { - throw new ERR_ACCESS_DENIED('process.binding'); - }; - process._linkedBinding = function _linkedBinding(_module) { - throw new ERR_ACCESS_DENIED('process._linkedBinding'); - }; - }, - - get manifest() { - if (typeof manifest === 'undefined') { - throw new ERR_MANIFEST_TDZ(); - } - return manifest; - }, - - get src() { - if (typeof manifestSrc === 'undefined') { - throw new ERR_MANIFEST_TDZ(); - } - return manifestSrc; - }, - - get url() { - if (typeof manifestURL === 'undefined') { - throw new ERR_MANIFEST_TDZ(); - } - return manifestURL; - }, - - assertIntegrity(moduleURL, content) { - this.manifest.assertIntegrity(moduleURL, content); - }, -}); diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 761d7ca5fdf8a1..133f2c07b53bc2 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -12,7 +12,6 @@ const { NumberParseInt, ObjectDefineProperty, ObjectFreeze, - SafeMap, String, StringPrototypeStartsWith, Symbol, @@ -34,7 +33,6 @@ const { } = require('internal/util'); const { - ERR_MANIFEST_ASSERT_INTEGRITY, ERR_MISSING_OPTION, ERR_ACCESS_DENIED, } = require('internal/errors').codes; @@ -57,7 +55,7 @@ function prepareMainThreadExecution(expandArgv1 = false, initializeModules = tru function prepareWorkerThreadExecution() { prepareExecution({ expandArgv1: false, - initializeModules: false, // Will need to initialize it after policy setup + initializeModules: false, isMainThread: false, }); } @@ -121,11 +119,6 @@ function prepareExecution(options) { if (isMainThread) { assert(internalBinding('worker').isMainThread); // Worker threads will get the manifest in the message handler. - const policy = readPolicyFromDisk(); - if (policy) { - require('internal/process/policy') - .setup(policy.manifestSrc, policy.manifestURL); - } // Print stack trace on `SIGINT` if option `--trace-sigint` presents. setupStacktracePrinterOnSigint(); @@ -580,56 +573,6 @@ function initializePermission() { } } -function readPolicyFromDisk() { - const experimentalPolicy = getOptionValue('--experimental-policy'); - if (experimentalPolicy) { - process.emitWarning('Policies are experimental.', - 'ExperimentalWarning'); - const { pathToFileURL, URL } = require('internal/url'); - // URL here as it is slightly different parsing - // no bare specifiers for now - let manifestURL; - if (require('path').isAbsolute(experimentalPolicy)) { - manifestURL = pathToFileURL(experimentalPolicy); - } else { - const cwdURL = pathToFileURL(process.cwd()); - cwdURL.pathname += '/'; - manifestURL = new URL(experimentalPolicy, cwdURL); - } - const fs = require('fs'); - const src = fs.readFileSync(manifestURL, 'utf8'); - const experimentalPolicyIntegrity = getOptionValue('--policy-integrity'); - if (experimentalPolicyIntegrity) { - const SRI = require('internal/policy/sri'); - const { createHash, timingSafeEqual } = require('crypto'); - const realIntegrities = new SafeMap(); - const integrityEntries = SRI.parse(experimentalPolicyIntegrity); - let foundMatch = false; - for (let i = 0; i < integrityEntries.length; i++) { - const { - algorithm, - value: expected, - } = integrityEntries[i]; - const hash = createHash(algorithm); - hash.update(src); - const digest = hash.digest(); - if (digest.length === expected.length && - timingSafeEqual(digest, expected)) { - foundMatch = true; - break; - } - realIntegrities.set(algorithm, digest.toString('base64')); - } - if (!foundMatch) { - throw new ERR_MANIFEST_ASSERT_INTEGRITY(manifestURL, realIntegrities); - } - } - return { - manifestSrc: src, manifestURL: manifestURL.href, - }; - } -} - function initializeCJSLoader() { const { initializeCJS } = require('internal/modules/cjs/loader'); initializeCJS(); diff --git a/lib/internal/worker.js b/lib/internal/worker.js index b58cbe56d01703..9eefaa97021756 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -41,7 +41,6 @@ const { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, } = errorCodes; -const { getOptionValue } = require('internal/options'); const workerIo = require('internal/worker/io'); const { @@ -273,12 +272,6 @@ class Worker extends EventEmitter { workerData: options.workerData, environmentData, publicPort: port2, - manifestURL: getOptionValue('--experimental-policy') ? - require('internal/process/policy').url : - null, - manifestSrc: getOptionValue('--experimental-policy') ? - require('internal/process/policy').src : - null, hasStdin: !!options.stdin, }, transferList); // Use this to cache the Worker's loopStart value once available. diff --git a/src/env.cc b/src/env.cc index 642a7d4a9d2559..b8574fcfb775b8 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1096,12 +1096,6 @@ void Environment::InitializeCompileCache() { dir_from_env.empty()) { return; } - if (!options()->experimental_policy.empty()) { - Debug(this, - DebugCategory::COMPILE_CACHE, - "[compile cache] skipping cache because policy is enabled"); - return; - } auto handler = std::make_unique(this); if (handler->InitializeDirectory(this, dir_from_env)) { compile_cache_handler_ = std::move(handler); diff --git a/src/env_properties.h b/src/env_properties.h index e10d472e730f34..8bf62103a394d9 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -35,8 +35,7 @@ V(napi_wrapper, "node:napi:wrapper") \ V(untransferable_object_private_symbol, "node:untransferableObject") \ V(exit_info_private_symbol, "node:exit_info_private_symbol") \ - V(promise_trace_id, "node:promise_trace_id") \ - V(require_private_symbol, "node:require_private_symbol") + V(promise_trace_id, "node:promise_trace_id") // Symbols are per-isolate primitives but Environment proxies them // for the sake of convenience. diff --git a/src/node_builtins.cc b/src/node_builtins.cc index bbb63df7899d4b..3a2320e33d0354 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -124,7 +124,6 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const { "_tls_wrap", "internal/tls/secure-pair", "internal/tls/parse-cert-string", "internal/tls/secure-context", "internal/http2/core", "internal/http2/compat", - "internal/policy/manifest", "internal/process/policy", "internal/streams/lazy_transform", #endif // !HAVE_OPENSSL "sys", // Deprecated. diff --git a/src/node_modules.cc b/src/node_modules.cc index c2f7a3da3ba009..5ff7fae9aebe1c 100644 --- a/src/node_modules.cc +++ b/src/node_modules.cc @@ -76,23 +76,21 @@ void BindingData::Deserialize(v8::Local context, } Local BindingData::PackageConfig::Serialize(Realm* realm) const { - auto has_manifest = !realm->env()->options()->experimental_policy.empty(); auto isolate = realm->isolate(); const auto ToString = [isolate](std::string_view input) -> Local { return String::NewFromUtf8( isolate, input.data(), NewStringType::kNormal, input.size()) .ToLocalChecked(); }; - Local values[7] = { + Local values[6] = { name.has_value() ? ToString(*name) : Undefined(isolate), main.has_value() ? ToString(*main) : Undefined(isolate), ToString(type), imports.has_value() ? ToString(*imports) : Undefined(isolate), exports.has_value() ? ToString(*exports) : Undefined(isolate), - has_manifest ? ToString(raw_json) : Undefined(isolate), ToString(file_path), }; - return Array::New(isolate, values, 7); + return Array::New(isolate, values, 6); } const BindingData::PackageConfig* BindingData::GetPackageJSON( @@ -361,11 +359,9 @@ void BindingData::GetNearestParentPackageJSONType( return; } - Local values[3] = { - ToV8Value(realm->context(), package_json->type).ToLocalChecked(), - ToV8Value(realm->context(), package_json->file_path).ToLocalChecked(), - ToV8Value(realm->context(), package_json->raw_json).ToLocalChecked()}; - args.GetReturnValue().Set(Array::New(realm->isolate(), values, 3)); + Local value = + ToV8Value(realm->context(), package_json->type).ToLocalChecked(); + args.GetReturnValue().Set(value); } void BindingData::GetPackageScopeConfig( diff --git a/src/node_options.cc b/src/node_options.cc index 60971e156209c3..684e32836aa8c8 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -107,14 +107,6 @@ void PerIsolateOptions::CheckOptions(std::vector* errors, void EnvironmentOptions::CheckOptions(std::vector* errors, std::vector* argv) { - if (has_policy_integrity_string && experimental_policy.empty()) { - errors->push_back("--policy-integrity requires " - "--experimental-policy be enabled"); - } - if (has_policy_integrity_string && experimental_policy_integrity.empty()) { - errors->push_back("--policy-integrity cannot be empty"); - } - if (!input_type.empty()) { if (input_type != "commonjs" && input_type != "module") { errors->push_back("--input-type must be \"module\" or \"commonjs\""); @@ -435,20 +427,6 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { &EnvironmentOptions::experimental_permission, kAllowedInEnvvar, false); - AddOption("--experimental-policy", - "use the specified file as a " - "security policy", - &EnvironmentOptions::experimental_policy, - kAllowedInEnvvar); - AddOption("[has_policy_integrity_string]", - "", - &EnvironmentOptions::has_policy_integrity_string); - AddOption("--policy-integrity", - "ensure the security policy contents match " - "the specified integrity", - &EnvironmentOptions::experimental_policy_integrity, - kAllowedInEnvvar); - Implies("--policy-integrity", "[has_policy_integrity_string]"); AddOption("--allow-fs-read", "allow permissions to read the filesystem", &EnvironmentOptions::allow_fs_read, diff --git a/src/node_options.h b/src/node_options.h index 892399954aa9e1..a15303962ff461 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -118,9 +118,6 @@ class EnvironmentOptions : public Options { bool experimental_import_meta_resolve = false; std::string input_type; // Value of --input-type std::string type; // Value of --experimental-default-type - std::string experimental_policy; - std::string experimental_policy_integrity; - bool has_policy_integrity_string = false; bool experimental_permission = false; std::vector allow_fs_read; std::vector allow_fs_write; diff --git a/test/benchmark/test-benchmark-policy.js b/test/benchmark/test-benchmark-policy.js deleted file mode 100644 index 7eb0992b1f1eda..00000000000000 --- a/test/benchmark/test-benchmark-policy.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -require('../common'); - -const runBenchmark = require('../common/benchmark'); - -runBenchmark('policy', [ - 'n=1', -]); diff --git a/test/fixtures/policy-manifest/createRequire-bypass.js b/test/fixtures/policy-manifest/createRequire-bypass.js deleted file mode 100644 index 06827ea0251d13..00000000000000 --- a/test/fixtures/policy-manifest/createRequire-bypass.js +++ /dev/null @@ -1,2 +0,0 @@ -const os = module.constructor.createRequire('file:///os-access-module.js')('os') -os.cpus() \ No newline at end of file diff --git a/test/fixtures/policy-manifest/invalid-module.js b/test/fixtures/policy-manifest/invalid-module.js deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/test/fixtures/policy-manifest/invalid.json b/test/fixtures/policy-manifest/invalid.json deleted file mode 100644 index a8a0deb2cf7e07..00000000000000 --- a/test/fixtures/policy-manifest/invalid.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "resources": { - "./fhqwhgads.js": { - "dependencies": { - "**": true - } - } - } -} diff --git a/test/fixtures/policy-manifest/main-constructor-bypass.js b/test/fixtures/policy-manifest/main-constructor-bypass.js deleted file mode 100644 index 066af168b1432e..00000000000000 --- a/test/fixtures/policy-manifest/main-constructor-bypass.js +++ /dev/null @@ -1,2 +0,0 @@ -const m = new require.main.constructor(); -m.require('./invalid-module') diff --git a/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js b/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js deleted file mode 100644 index 7e002dc17a7741..00000000000000 --- a/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js +++ /dev/null @@ -1,2 +0,0 @@ -const m = new require.main.constructor(); -require.extensions['.js'](m, './invalid-module') diff --git a/test/fixtures/policy-manifest/main-module-bypass.js b/test/fixtures/policy-manifest/main-module-bypass.js deleted file mode 100644 index a1cec2ddd3ffbb..00000000000000 --- a/test/fixtures/policy-manifest/main-module-bypass.js +++ /dev/null @@ -1 +0,0 @@ -process.mainModule.require('os').cpus(); diff --git a/test/fixtures/policy-manifest/main-module-proto-bypass.js b/test/fixtures/policy-manifest/main-module-proto-bypass.js deleted file mode 100644 index 6111aae140ed0a..00000000000000 --- a/test/fixtures/policy-manifest/main-module-proto-bypass.js +++ /dev/null @@ -1 +0,0 @@ -process.mainModule.__proto__.require("os") diff --git a/test/fixtures/policy-manifest/manifest-impersonate.json b/test/fixtures/policy-manifest/manifest-impersonate.json deleted file mode 100644 index 5e15b6e9cca906..00000000000000 --- a/test/fixtures/policy-manifest/manifest-impersonate.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "resources": { - "./createRequire-bypass.js": { - "integrity": true - }, - "/os-access-module.js": { - "integrity": true, - "dependencies": { - "os": true - } - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy-manifest/module-constructor-bypass.js b/test/fixtures/policy-manifest/module-constructor-bypass.js deleted file mode 100644 index 8ff9e532a81f71..00000000000000 --- a/test/fixtures/policy-manifest/module-constructor-bypass.js +++ /dev/null @@ -1 +0,0 @@ -module.constructor._load('node:child_process'); diff --git a/test/fixtures/policy-manifest/object-define-property-bypass.js b/test/fixtures/policy-manifest/object-define-property-bypass.js deleted file mode 100644 index 5543fd35b28b26..00000000000000 --- a/test/fixtures/policy-manifest/object-define-property-bypass.js +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index 24bd92817d24b1..00000000000000 --- a/test/fixtures/policy-manifest/onerror-exit.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "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 deleted file mode 100644 index f08bc8d32c07e7..00000000000000 --- a/test/fixtures/policy-manifest/onerror-resource-exit.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "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 deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/test/fixtures/policy/bad-main.mjs b/test/fixtures/policy/bad-main.mjs deleted file mode 100644 index db1938ad7c2e87..00000000000000 --- a/test/fixtures/policy/bad-main.mjs +++ /dev/null @@ -1 +0,0 @@ -import {doesNotExist} from './dep.js'; diff --git a/test/fixtures/policy/canonicalize.mjs b/test/fixtures/policy/canonicalize.mjs deleted file mode 100644 index 64e7cd117ad7c0..00000000000000 --- a/test/fixtures/policy/canonicalize.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import resolveAsFS from './dep.js'; -import fs from 'fs'; - -let correct = resolveAsFS === fs && typeof resolveAsFS === 'object'; -process.exit(correct ? 0 : 1); diff --git a/test/fixtures/policy/crypto-default-encoding/.gitattributes b/test/fixtures/policy/crypto-default-encoding/.gitattributes deleted file mode 100644 index cbdcbbc258e6e7..00000000000000 --- a/test/fixtures/policy/crypto-default-encoding/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.js text eol=lf diff --git a/test/fixtures/policy/crypto-default-encoding/dep.js b/test/fixtures/policy/crypto-default-encoding/dep.js deleted file mode 100644 index d741da76db0076..00000000000000 --- a/test/fixtures/policy/crypto-default-encoding/dep.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -// No code. diff --git a/test/fixtures/policy/crypto-default-encoding/parent.js b/test/fixtures/policy/crypto-default-encoding/parent.js deleted file mode 100644 index 90ebde7e6535c0..00000000000000 --- a/test/fixtures/policy/crypto-default-encoding/parent.js +++ /dev/null @@ -1,4 +0,0 @@ -'use strict'; - -require('crypto').DEFAULT_ENCODING = process.env.DEFAULT_ENCODING; -require('./dep.js'); diff --git a/test/fixtures/policy/crypto-default-encoding/policy.json b/test/fixtures/policy/crypto-default-encoding/policy.json deleted file mode 100644 index 4cb485e1d9e2e4..00000000000000 --- a/test/fixtures/policy/crypto-default-encoding/policy.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "resources": { - "./parent.js": { - "integrity": "sha384-j4pMdq83q5Bq9+idcHuGKzi89FrYm1PhZYrEw3irbNob6g4i3vKBjfYiRNYwmoGr", - "dependencies": { - "crypto": true, - "./dep.js": true - } - }, - "./dep.js": { - "integrity": "sha384-VU7GIrTix/HFLhUb4yqsV4n1xXqjPcWw6kLvjuKXtR1+9nmufJu5vZLajBs8brIW" - } - } -} diff --git a/test/fixtures/policy/crypto-hash-tampering/.gitattributes b/test/fixtures/policy/crypto-hash-tampering/.gitattributes deleted file mode 100644 index cbdcbbc258e6e7..00000000000000 --- a/test/fixtures/policy/crypto-hash-tampering/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.js text eol=lf diff --git a/test/fixtures/policy/crypto-hash-tampering/main.js b/test/fixtures/policy/crypto-hash-tampering/main.js deleted file mode 100644 index 2ee233fe75461b..00000000000000 --- a/test/fixtures/policy/crypto-hash-tampering/main.js +++ /dev/null @@ -1,8 +0,0 @@ -const h = require('crypto').createHash('sha384'); -const fakeDigest = h.digest(); - -const kHandle = Object.getOwnPropertySymbols(h) - .find((s) => s.description === 'kHandle'); -h[kHandle].constructor.prototype.digest = () => fakeDigest; - -require('./protected.js'); diff --git a/test/fixtures/policy/crypto-hash-tampering/policy.json b/test/fixtures/policy/crypto-hash-tampering/policy.json deleted file mode 100644 index 3d981911533f56..00000000000000 --- a/test/fixtures/policy/crypto-hash-tampering/policy.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "resources": { - "./main.js": { - "integrity": true, - "dependencies": { - "./protected.js": true, - "crypto": true - } - }, - "./protected.js": { - "integrity": "sha384-OLBgp1GsljhM2TJ+sbHjaiH9txEUvgdDTAzHv2P24donTt6/529l+9Ua0vFImLlb", - "dependencies": true - } - } -} diff --git a/test/fixtures/policy/crypto-hash-tampering/protected.js b/test/fixtures/policy/crypto-hash-tampering/protected.js deleted file mode 100644 index 2b57adba5b191a..00000000000000 --- a/test/fixtures/policy/crypto-hash-tampering/protected.js +++ /dev/null @@ -1 +0,0 @@ -console.log(require('fs').readFileSync('/etc/passwd').length); diff --git a/test/fixtures/policy/dep-policy.json b/test/fixtures/policy/dep-policy.json deleted file mode 100644 index 6cc483a578733c..00000000000000 --- a/test/fixtures/policy/dep-policy.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "resources": { - "./dep.js": { - "integrity": "sha512-7CMcc2oytFfMnGQaXbJk84gYWF2J7p/fmWPW7dsnJyniD+vgxtK9VAZ/22UxFOA4q5d27RoGLxSqNZ/nGCJkMw== sha512-scgN9Td0bGMlGH2lUHvEeHtz92Hx6AO+sYhU3WRI6bn3jEUCXbXJs68nOOsGzRWR7a2tbqGoETnOCpHHf1Njhw==" - } - } -} diff --git a/test/fixtures/policy/dep.js b/test/fixtures/policy/dep.js deleted file mode 100644 index 1c61a090d275a8..00000000000000 --- a/test/fixtures/policy/dep.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; -module.exports = 'The Secret Ingredient'; diff --git a/test/fixtures/policy/dependencies/dependencies-empty-policy.json b/test/fixtures/policy/dependencies/dependencies-empty-policy.json deleted file mode 100644 index 9c0389cd03928f..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-empty-policy.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": {} - }, - "../dep.js": { - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-missing-export-policy.json b/test/fixtures/policy/dependencies/dependencies-missing-export-policy.json deleted file mode 100644 index 5da0de13920936..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-missing-export-policy.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "resources": { - "../bad-main.mjs": { - "integrity": true, - "dependencies": true - }, - "../dep.js": { - "integrity": true - } - } -} diff --git a/test/fixtures/policy/dependencies/dependencies-missing-policy-default-true.json b/test/fixtures/policy/dependencies/dependencies-missing-policy-default-true.json deleted file mode 100644 index 10ab862d2b0cc0..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-missing-policy-default-true.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "dependencies": true, - "resources": { - "../parent.js": { - "cascade": true, - "integrity": true - }, - "../dep.js": { - "cascade": true, - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-missing-policy.json b/test/fixtures/policy/dependencies/dependencies-missing-policy.json deleted file mode 100644 index 40d2866ba5e06d..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-missing-policy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true - }, - "../dep.js": { - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-redirect-builtin-policy.json b/test/fixtures/policy/dependencies/dependencies-redirect-builtin-policy.json deleted file mode 100644 index 7d2715f96a5092..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-redirect-builtin-policy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": { - "../dep.js": "node:util" - } - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-redirect-policy.json b/test/fixtures/policy/dependencies/dependencies-redirect-policy.json deleted file mode 100644 index c5b73403d5f864..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-redirect-policy.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": { - "../dep.js": "../dep.js" - } - }, - "../dep.js": { - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-redirect-unknown-builtin-policy.json b/test/fixtures/policy/dependencies/dependencies-redirect-unknown-builtin-policy.json deleted file mode 100644 index a2bcb2676223d3..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-redirect-unknown-builtin-policy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": { - "../dep.js": "node:404" - } - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-scopes-and-resources-policy.json b/test/fixtures/policy/dependencies/dependencies-scopes-and-resources-policy.json deleted file mode 100644 index 0cf33b16363352..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-scopes-and-resources-policy.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "resources": { - "../multi-deps.js": { - "integrity": true, - "cascade": true - } - }, - "scopes": { - "../": { - "integrity": true, - "dependencies": true - } - } -} diff --git a/test/fixtures/policy/dependencies/dependencies-scopes-policy.json b/test/fixtures/policy/dependencies/dependencies-scopes-policy.json deleted file mode 100644 index 2af78b0fa945bf..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-scopes-policy.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "scopes": { - "../": { - "integrity": true, - "dependencies": true - } - } -} diff --git a/test/fixtures/policy/dependencies/dependencies-scopes-relative-specifier.json b/test/fixtures/policy/dependencies/dependencies-scopes-relative-specifier.json deleted file mode 100644 index 7ce0b0262e48a8..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-scopes-relative-specifier.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "scopes": { - "file:": { - "integrity": true, - "cascade": true, - "dependencies": { - "fs": "node:fs", - "../dep.js": "node:fs" - } - } - } -} diff --git a/test/fixtures/policy/dependencies/dependencies-wildcard-policy.json b/test/fixtures/policy/dependencies/dependencies-wildcard-policy.json deleted file mode 100644 index 04ae9a318f6dc0..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-wildcard-policy.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": true - }, - "../dep.js": { - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/main.mjs b/test/fixtures/policy/main.mjs deleted file mode 100644 index feac7e3b8c946c..00000000000000 --- a/test/fixtures/policy/main.mjs +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; -export default 'main.mjs'; diff --git a/test/fixtures/policy/multi-deps.js b/test/fixtures/policy/multi-deps.js deleted file mode 100644 index 51cdf61783989a..00000000000000 --- a/test/fixtures/policy/multi-deps.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; -require('fs'); -require('process'); diff --git a/test/fixtures/policy/parent.js b/test/fixtures/policy/parent.js deleted file mode 100644 index b821bfee184a35..00000000000000 --- a/test/fixtures/policy/parent.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; -// Included in parent-policy.json -require('./dep.js'); diff --git a/test/fixtures/policy/process-binding/app.js b/test/fixtures/policy/process-binding/app.js deleted file mode 100644 index 16e26d12a16028..00000000000000 --- a/test/fixtures/policy/process-binding/app.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -const assert = require('assert'); - -assert.throws(() => { process.binding(); }, { - code: 'ERR_ACCESS_DENIED' -}); -assert.throws(() => { process._linkedBinding(); }, { - code: 'ERR_ACCESS_DENIED' -}); diff --git a/test/fixtures/policy/process-binding/policy.json b/test/fixtures/policy/process-binding/policy.json deleted file mode 100644 index 81f8b40c90a069..00000000000000 --- a/test/fixtures/policy/process-binding/policy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "resources": { - "./app.js": { - "integrity": true, - "dependencies": { - "assert": true - } - } - } -} diff --git a/test/node-api/test_policy/binding.c b/test/node-api/test_policy/binding.c deleted file mode 100644 index 3be31e18456bd9..00000000000000 --- a/test/node-api/test_policy/binding.c +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include "../../js-native-api/common.h" -#include - -static napi_value Method(napi_env env, napi_callback_info info) { - napi_value world; - const char* str = "world"; - size_t str_len = strlen(str); - NODE_API_CALL(env, napi_create_string_utf8(env, str, str_len, &world)); - return world; -} - -NAPI_MODULE_INIT() { - napi_property_descriptor desc = DECLARE_NODE_API_PROPERTY("hello", Method); - NODE_API_CALL(env, napi_define_properties(env, exports, 1, &desc)); - return exports; -} diff --git a/test/node-api/test_policy/binding.gyp b/test/node-api/test_policy/binding.gyp deleted file mode 100644 index 62381d5e54f22b..00000000000000 --- a/test/node-api/test_policy/binding.gyp +++ /dev/null @@ -1,8 +0,0 @@ -{ - "targets": [ - { - "target_name": "binding", - "sources": [ "binding.c" ] - } - ] -} diff --git a/test/node-api/test_policy/test_policy.js b/test/node-api/test_policy/test_policy.js deleted file mode 100644 index f14ceff3c4537b..00000000000000 --- a/test/node-api/test_policy/test_policy.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; -const common = require('../../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); - -const assert = require('assert'); -const tmpdir = require('../../common/tmpdir'); -const { spawnSync } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const { pathToFileURL } = require('url'); - -tmpdir.refresh(); - -function hash(algo, body) { - const h = crypto.createHash(algo); - h.update(body); - return h.digest('base64'); -} - -const policyFilepath = tmpdir.resolve('policy'); - -const depFilepath = require.resolve(`./build/${common.buildType}/binding.node`); -const depURL = pathToFileURL(depFilepath); - -const depBody = fs.readFileSync(depURL); -function writePolicy(...resources) { - const manifest = { resources: {} }; - for (const { url, integrity } of resources) { - manifest.resources[url] = { integrity }; - } - fs.writeFileSync(policyFilepath, JSON.stringify(manifest, null, 2)); -} - - -function test(shouldFail, resources) { - writePolicy(...resources); - const { status, stdout, stderr } = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - depFilepath, - ]); - - console.log(stdout.toString(), stderr.toString()); - if (shouldFail) { - assert.notStrictEqual(status, 0); - } else { - assert.strictEqual(status, 0); - } -} - -test(false, [{ - url: depURL, - integrity: `sha256-${hash('sha256', depBody)}`, -}]); -test(true, [{ - url: depURL, - integrity: `sha256akjsalkjdlaskjdk-${hash('sha256', depBody)}`, -}]); diff --git a/test/parallel/test-compile-cache-policy.js b/test/parallel/test-compile-cache-policy.js deleted file mode 100644 index fd6fe36bcc0cd9..00000000000000 --- a/test/parallel/test-compile-cache-policy.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -// This tests NODE_COMPILE_CACHE is disabled when policy is used. - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); - -const { spawnSyncAndAssert } = require('../common/child_process'); -const assert = require('assert'); -const fs = require('fs'); -const tmpdir = require('../common/tmpdir'); -const fixtures = require('../common/fixtures'); - -{ - tmpdir.refresh(); - const dir = tmpdir.resolve('.compile_cache_dir'); - const script = fixtures.path('policy', 'parent.js'); - const policy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-redirect-policy.json'); - spawnSyncAndAssert( - process.execPath, - ['--experimental-policy', policy, script], - { - env: { - ...process.env, - NODE_DEBUG_NATIVE: 'COMPILE_CACHE', - NODE_COMPILE_CACHE: dir - }, - cwd: tmpdir.path - }, - { - stderr: /skipping cache because policy is enabled/ - }); - assert(!fs.existsSync(dir)); -} diff --git a/test/parallel/test-policy-crypto-default-encoding.js b/test/parallel/test-policy-crypto-default-encoding.js deleted file mode 100644 index 1f62b4d85a3c4f..00000000000000 --- a/test/parallel/test-policy-crypto-default-encoding.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); - -const encodings = ['buffer', 'utf8', 'utf16le', 'latin1', 'base64', 'hex']; - -for (const encoding of encodings) { - const dep = fixtures.path('policy', 'crypto-default-encoding', 'parent.js'); - const depPolicy = fixtures.path( - 'policy', - 'crypto-default-encoding', - 'policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ], - { - env: { - ...process.env, - DEFAULT_ENCODING: encoding - } - } - ); - assert.strictEqual(status, 0); -} diff --git a/test/parallel/test-policy-crypto-hash-tampering.js b/test/parallel/test-policy-crypto-hash-tampering.js deleted file mode 100644 index 96066defb59a1b..00000000000000 --- a/test/parallel/test-policy-crypto-hash-tampering.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); - -const mainPath = fixtures.path('policy', 'crypto-hash-tampering', 'main.js'); -const policyPath = fixtures.path( - 'policy', - 'crypto-hash-tampering', - 'policy.json'); -const { status, stderr } = - spawnSync(process.execPath, ['--experimental-policy', policyPath, mainPath], { encoding: 'utf8' }); -assert.strictEqual(status, 1); -assert(stderr.includes('sha384-Bnp/T8gFNzT9mHj2G/AeuMH8LcAQ4mljw15nxBNl5yaGM7VgbMzDT7O4+dXZTJJn')); diff --git a/test/parallel/test-policy-dependencies.js b/test/parallel/test-policy-dependencies.js deleted file mode 100644 index decc164dadd253..00000000000000 --- a/test/parallel/test-policy-dependencies.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); - -const dep = fixtures.path('policy', 'parent.js'); -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-redirect-policy.json'); - const { status, stderr, stdout } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - console.log('%s\n%s', stderr, stdout); - assert.strictEqual(status, 0); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-redirect-builtin-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 0); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-redirect-unknown-builtin-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 1); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-wildcard-policy.json'); - const { status, stderr, stdout } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - console.log('%s\n%s', stderr, stdout); - assert.strictEqual(status, 0); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-empty-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 1); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-missing-policy-default-true.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 0); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-missing-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 1); -} -{ - // Regression test for https://github.com/nodejs/node/issues/37812 - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-missing-export-policy.json'); - const { status, stderr } = spawnSync( - process.execPath, - [ - '--experimental-policy', - depPolicy, - fixtures.path('policy', 'bad-main.mjs'), - ] - ); - assert.strictEqual(status, 1); - assert.match( - `${stderr}`, - /SyntaxError: Named export 'doesNotExist' not found\./, - new Error('Should give the real SyntaxError and position')); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-scopes-relative-specifier.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', - depPolicy, - fixtures.path('policy', 'canonicalize.mjs'), - ] - ); - assert.strictEqual( - status, - 0, - new Error( - 'policies should canonicalize specifiers by default prior to matching') - ); -} diff --git a/test/parallel/test-policy-dependency-conditions.js b/test/parallel/test-policy-dependency-conditions.js deleted file mode 100644 index dec17aa22984b0..00000000000000 --- a/test/parallel/test-policy-dependency-conditions.js +++ /dev/null @@ -1,123 +0,0 @@ -'use strict'; -// Flags: --expose-internals - -const common = require('../common'); - -if (!common.hasCrypto) common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const Manifest = require('internal/policy/manifest').Manifest; - - -const assert = require('assert'); - -const { debuglog } = require('util'); -const debug = debuglog('test'); - -const conditionTreePermutations = [ - ['default'], - ['import'], - ['node'], - ['require'], - ['require', 'import'], - ['import', 'require'], - ['default', 'require'], - ['require', 'default'], - ['node', 'require'], - ['require', 'node'], -]; -for (const totalDepth of [1, 2, 3]) { - const conditionTrees = []; - function calc(depthLeft = 0, path = []) { - if (depthLeft) { - for (const conditions of conditionTreePermutations) { - calc(depthLeft - 1, [...path, conditions]); - } - } else { - conditionTrees.push(path); - } - } - calc(totalDepth, []); - let nextURLId = 1; - function getUniqueHREF() { - const id = nextURLId++; - return `test:${id}`; - } - for (const tree of conditionTrees) { - const root = {}; - const targets = [root]; - const offsets = [-1]; - const order = []; - while (offsets.length) { - const depth = offsets.length - 1; - offsets[depth]++; - const conditionOffset = offsets[depth]; - const conditionsForDepth = tree[depth]; - if (conditionOffset >= conditionsForDepth.length) { - offsets.pop(); - continue; - } - let target; - if (depth === tree.length - 1) { - target = getUniqueHREF(); - order.push({ - target, - conditions: new Set( - offsets.map( - (conditionOffset, depth) => tree[depth][conditionOffset] - ) - ) - }); - } else { - target = {}; - targets[depth + 1] = target; - offsets.push(-1); - } - const condition = tree[depth][conditionOffset]; - targets[depth][condition] = target; - } - const manifest = new Manifest({ - resources: { - 'test:_': { - dependencies: { - _: root - } - } - } - }); - const redirector = manifest.getDependencyMapper('test:_'); - for (const { target, conditions } of order) { - const result = redirector.resolve('_', conditions).href; - if (result !== target) { - // If we didn't hit the target, make sure a target prior to this one - // matched, including conditions - searchPriorTargets: - for (const { target: preTarget, conditions: preConditions } of order) { - if (result === preTarget) { - // Ensure that the current conditions are a super set of the - // prior target - for (const preCondition of preConditions) { - if (conditions.has(preCondition) !== true) { - continue searchPriorTargets; - } - } - break searchPriorTargets; - } - if (preTarget === target) { - debug( - 'dependencies %O expected ordering %O and trying for %j ' + - 'no prior targets matched', - root, - order, - target - ); - // THIS WILL ALWAYS FAIL, but we want that error message - assert.strictEqual( - result, target - ); - } - } - } - } - } -} diff --git a/test/parallel/test-policy-integrity-flag.js b/test/parallel/test-policy-integrity-flag.js deleted file mode 100644 index 1b4b013b900edd..00000000000000 --- a/test/parallel/test-policy-integrity-flag.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); -const fs = require('fs'); -const crypto = require('crypto'); - -const depPolicy = fixtures.path('policy', 'dep-policy.json'); -const dep = fixtures.path('policy', 'dep.js'); - -const emptyHash = crypto.createHash('sha512'); -emptyHash.update(''); -const emptySRI = `sha512-${emptyHash.digest('base64')}`; -const policyHash = crypto.createHash('sha512'); -policyHash.update(fs.readFileSync(depPolicy)); - -/* eslint-disable @stylistic/js/max-len */ -// When using \n only -const nixPolicySRI = 'sha512-u/nXI6UacK5fKDC2bopcgnuQY4JXJKlK3dESO3GIKKxwogVHjJqpF9rgk7Zw+TJXIc96xBUWKHuUgOzic8/4tQ=='; -// When \n is turned into \r\n -const windowsPolicySRI = 'sha512-OeyCPRo4OZMosHyquZXDHpuU1F4KzG9UHFnn12FMaHsvqFUt3TFZ+7wmZE7ThZ5rsQWkUjc9ZH0knGZ2e8BYPQ=='; -/* eslint-enable @stylistic/js/max-len */ - -const depPolicySRI = `${nixPolicySRI} ${windowsPolicySRI}`; -{ - const { status, stderr } = spawnSync( - process.execPath, - [ - '--policy-integrity', emptySRI, - '--experimental-policy', depPolicy, dep, - ] - ); - - assert.ok(stderr.includes('ERR_MANIFEST_ASSERT_INTEGRITY')); - assert.strictEqual(status, 1); -} -{ - const { status, stderr } = spawnSync( - process.execPath, - [ - '--policy-integrity', '', - '--experimental-policy', depPolicy, dep, - ] - ); - - assert.ok(stderr.includes('--policy-integrity')); - assert.strictEqual(status, 9); -} -{ - const { status, stderr } = spawnSync( - process.execPath, - [ - '--policy-integrity', depPolicySRI, - '--experimental-policy', depPolicy, dep, - ] - ); - - assert.strictEqual(status, 0, `status: ${status}\nstderr: ${stderr}`); -} diff --git a/test/parallel/test-policy-manifest.js b/test/parallel/test-policy-manifest.js deleted file mode 100644 index 3c4b1695d28835..00000000000000 --- a/test/parallel/test-policy-manifest.js +++ /dev/null @@ -1,157 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) - common.skip('missing crypto'); - -common.requireNoPackageJSONAbove(); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); -const { cpSync, rmSync } = require('fs'); -const fixtures = require('../common/fixtures.js'); -const tmpdir = require('../common/tmpdir.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 "\*"/); -} - -{ - tmpdir.refresh(); - const policyFilepath = tmpdir.resolve('file with % in its name.json'); - cpSync(fixtures.path('policy-manifest', 'invalid.json'), policyFilepath); - 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 "\*"/); - rmSync(policyFilepath); -} - -{ - 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); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); - const mainModuleBypass = fixtures.path('policy-manifest', 'main-module-proto-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-exit.json'); - const mainModuleBypass = fixtures.path('policy-manifest', 'module-constructor-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - mainModuleBypass, - ]); - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /TypeError/); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'manifest-impersonate.json'); - const createRequireBypass = fixtures.path('policy-manifest', 'createRequire-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - createRequireBypass, - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /TypeError/); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); - const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - mainModuleBypass, - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /TypeError/); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); - const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-extensions-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - mainModuleBypass, - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /TypeError/); -} diff --git a/test/parallel/test-policy-parse-integrity.js b/test/parallel/test-policy-parse-integrity.js deleted file mode 100644 index 3295bed28fb5a8..00000000000000 --- a/test/parallel/test-policy-parse-integrity.js +++ /dev/null @@ -1,111 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -tmpdir.refresh(); - -function hash(algo, body) { - const h = crypto.createHash(algo); - h.update(body); - return h.digest('base64'); -} - -const tmpdirPath = tmpdir.resolve('test-policy-parse-integrity'); -fs.rmSync(tmpdirPath, { maxRetries: 3, recursive: true, force: true }); -fs.mkdirSync(tmpdirPath, { recursive: true }); - -const policyFilepath = path.join(tmpdirPath, 'policy'); - -const parentFilepath = path.join(tmpdirPath, 'parent.js'); -const parentBody = "require('./dep.js')"; - -const depFilepath = path.join(tmpdirPath, 'dep.js'); -const depURL = pathToFileURL(depFilepath); -const depBody = ''; - -fs.writeFileSync(parentFilepath, parentBody); -fs.writeFileSync(depFilepath, depBody); - -const tmpdirURL = pathToFileURL(tmpdirPath); -if (!tmpdirURL.pathname.endsWith('/')) { - tmpdirURL.pathname += '/'; -} - -const packageFilepath = path.join(tmpdirPath, 'package.json'); -const packageURL = pathToFileURL(packageFilepath); -const packageBody = '{"main": "dep.js"}'; - -function test({ shouldFail, integrity, manifest = {} }) { - manifest.resources = {}; - const resources = { - [packageURL]: { - body: packageBody, - integrity: `sha256-${hash('sha256', packageBody)}` - }, - [depURL]: { - body: depBody, - integrity - } - }; - for (const [url, { body, integrity }] of Object.entries(resources)) { - manifest.resources[url] = { - integrity, - }; - fs.writeFileSync(new URL(url, tmpdirURL.href), body); - } - fs.writeFileSync(policyFilepath, JSON.stringify(manifest, null, 2)); - const { status } = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - depFilepath, - ]); - if (shouldFail) { - assert.notStrictEqual(status, 0); - } else { - assert.strictEqual(status, 0); - } -} - -test({ - shouldFail: false, - integrity: `sha256-${hash('sha256', depBody)}`, -}); -test({ - shouldFail: true, - integrity: `1sha256-${hash('sha256', depBody)}`, -}); -test({ - shouldFail: true, - integrity: 'hoge', -}); -test({ - shouldFail: true, - integrity: `sha256-${hash('sha256', depBody)}sha256-${hash( - 'sha256', - depBody - )}`, -}); -test({ - shouldFail: true, - integrity: `sha256-${hash('sha256', 'file:///')}`, - manifest: { - onerror: 'exit' - } -}); -test({ - shouldFail: false, - integrity: `sha256-${hash('sha256', 'file:///')}`, - manifest: { - onerror: 'log' - } -}); diff --git a/test/parallel/test-policy-process-binding.js b/test/parallel/test-policy-process-binding.js deleted file mode 100644 index 34a6954e6c47c8..00000000000000 --- a/test/parallel/test-policy-process-binding.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const common = require('../common'); -common.requireNoPackageJSONAbove(); - -if (!common.hasCrypto) - common.skip('missing crypto'); - -const fixtures = require('../common/fixtures'); - -const assert = require('node:assert'); -const { spawnSync } = require('node:child_process'); - -const dep = fixtures.path('policy', 'process-binding', 'app.js'); -const depPolicy = fixtures.path( - 'policy', - 'process-binding', - 'policy.json'); -const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ], - { - stdio: 'inherit' - }, -); -assert.strictEqual(status, 0); diff --git a/test/parallel/test-policy-scopes-dependencies.js b/test/parallel/test-policy-scopes-dependencies.js deleted file mode 100644 index f4b93a08e58c47..00000000000000 --- a/test/parallel/test-policy-scopes-dependencies.js +++ /dev/null @@ -1,342 +0,0 @@ -'use strict'; -// Flags: --expose-internals - -const common = require('../common'); - -if (!common.hasCrypto) common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const Manifest = require('internal/policy/manifest').Manifest; -const assert = require('assert'); - -// #region files -{ - const baseURLs = [ - // Localhost is special cased in spec - 'file://localhost/root', - 'file:///root', - 'file:///', - 'file:///root/dir1', - 'file:///root/dir1/', - 'file:///root/dir1/dir2', - 'file:///root/dir1/dir2/', - ]; - - { - const manifest = new Manifest({ - scopes: { - 'file:///': { - dependencies: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - '': { - dependencies: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - '': { - dependencies: true - }, - 'file:': { - cascade: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'file:': { - dependencies: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest - .getDependencyMapper(href) - .resolve('fs'), - true); - } - - assert.strictEqual( - manifest - .getDependencyMapper('file://host/') - .resolve('fs'), - true); - } - { - const manifest = new Manifest({ - resources: { - 'file:///root/dir1': { - dependencies: { - fs: 'test:fs1' - } - }, - 'file:///root/dir1/isolated': {}, - 'file:///root/dir1/cascade': { - cascade: true - } - }, - scopes: { - 'file:///root/dir1/': { - dependencies: { - fs: 'test:fs2' - } - }, - 'file:///root/dir1/censor/': { - }, - } - }); - - for (const href of baseURLs) { - const redirector = manifest.getDependencyMapper(href); - if (href.startsWith('file:///root/dir1/')) { - assert.strictEqual( - redirector.resolve('fs').href, - 'test:fs2' - ); - } else if (href === 'file:///root/dir1') { - assert.strictEqual( - redirector.resolve('fs').href, - 'test:fs1' - ); - } else { - assert.strictEqual(redirector.resolve('fs'), null); - } - } - - assert.strictEqual( - manifest - .getDependencyMapper('file:///root/dir1/isolated') - .resolve('fs'), - null - ); - assert.strictEqual( - manifest - .getDependencyMapper('file:///root/dir1/cascade') - .resolve('fs').href, - 'test:fs2' - ); - assert.strictEqual( - manifest - .getDependencyMapper('file:///root/dir1/censor/foo') - .resolve('fs'), - null - ); - } -} -// #endregion -// #region data -{ - const baseURLs = [ - 'data:text/javascript,0', - 'data:text/javascript,0/1', - ]; - - { - const manifest = new Manifest({ - scopes: { - 'data:text/': { - dependencies: { - fs: true - } - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - null); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:/': { - dependencies: { - fs: true - } - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - null); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:': { - dependencies: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:text/javascript,0/': { - dependencies: { - fs: 'test:fs1' - } - }, - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - null); - } - } -} -// #endregion -// #region blob -{ - { - const manifest = new Manifest({ - scopes: { - 'https://example.com/': { - dependencies: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - 'https://example.com': { - dependencies: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - null); - } - { - const manifest = new Manifest({ - scopes: { - 'blob:https://example.com/has-origin': { - cascade: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - null); - } - { - const manifest = new Manifest({ - scopes: { - // FIXME - 'https://example.com/': { - dependencies: true - }, - 'blob:https://example.com/has-origin': { - cascade: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - 'blob:': { - dependencies: true - }, - 'blob:https://example.com/has-origin': { - cascade: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - null); - assert.strictEqual( - manifest - .getDependencyMapper('blob:foo').resolve('fs'), - true - ); - } -} -// #endregion diff --git a/test/parallel/test-policy-scopes-integrity.js b/test/parallel/test-policy-scopes-integrity.js deleted file mode 100644 index 16d9eb04eb7347..00000000000000 --- a/test/parallel/test-policy-scopes-integrity.js +++ /dev/null @@ -1,316 +0,0 @@ -'use strict'; -// Flags: --expose-internals - -const common = require('../common'); - -if (!common.hasCrypto) common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const Manifest = require('internal/policy/manifest').Manifest; -const assert = require('assert'); - -// #region files -{ - const baseURLs = [ - // Localhost is special cased in spec - 'file://localhost/root', - 'file:///root', - 'file:///', - 'file:///root/dir1', - 'file:///root/dir1/', - 'file:///root/dir1/dir2', - 'file:///root/dir1/dir2/', - ]; - - { - const manifest = new Manifest({ - scopes: { - 'file:///': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.assertIntegrity(href), - true - ); - assert.strictEqual( - manifest.assertIntegrity(href, null), - true - ); - assert.strictEqual( - manifest.assertIntegrity(href, ''), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'file:': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.assertIntegrity(href), - true - ); - assert.strictEqual( - manifest.assertIntegrity(href, null), - true - ); - assert.strictEqual( - manifest.assertIntegrity(href, ''), - true - ); - } - } - { - const manifest = new Manifest({ - resources: { - 'file:///root/dir1/isolated': {}, - 'file:///root/dir1/cascade': { - cascade: true - } - }, - scopes: { - 'file:///root/dir1/': { - integrity: true, - }, - 'file:///root/dir1/dir2/': { - cascade: true, - }, - 'file:///root/dir1/censor/': { - }, - } - }); - assert.throws( - () => { - manifest.assertIntegrity('file:///root/dir1/isolated'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - assert.strictEqual( - manifest.assertIntegrity('file:///root/dir1/cascade'), - true - ); - assert.strictEqual( - manifest.assertIntegrity('file:///root/dir1/enoent'), - true - ); - assert.strictEqual( - manifest.assertIntegrity('file:///root/dir1/dir2/enoent'), - true - ); - assert.throws( - () => { - manifest.assertIntegrity('file:///root/dir1/censor/enoent'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } -} -// #endregion -// #region data -{ - const baseURLs = [ - 'data:text/javascript,0', - 'data:text/javascript,0/1', - ]; - - { - const manifest = new Manifest({ - scopes: { - 'data:text/': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.throws( - () => { - manifest.assertIntegrity(href); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:/': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.throws( - () => { - manifest.assertIntegrity(href); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual(manifest.assertIntegrity(href), true); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:text/javascript,0/': { - integrity: true - }, - } - }); - - for (const href of baseURLs) { - assert.throws( - () => { - manifest.assertIntegrity(href); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - } -} -// #endregion -// #region blob -{ - { - const manifest = new Manifest({ - scopes: { - 'https://example.com/': { - integrity: true - } - } - }); - - assert.strictEqual( - manifest.assertIntegrity('blob:https://example.com/has-origin'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - } - }); - - assert.throws( - () => { - manifest.assertIntegrity('blob:https://example.com/has-origin'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - { - const manifest = new Manifest({ - scopes: { - 'blob:https://example.com/has-origin': { - cascade: true - } - } - }); - - assert.throws( - () => { - manifest.assertIntegrity('blob:https://example.com/has-origin'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - { - const manifest = new Manifest({ - resources: { - 'blob:https://example.com/has-origin': { - cascade: true - } - }, - scopes: { - 'https://example.com': { - integrity: true - } - } - }); - - assert.strictEqual( - manifest.assertIntegrity('blob:https://example.com/has-origin'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - 'blob:': { - integrity: true - }, - 'https://example.com': { - cascade: true - } - } - }); - - assert.throws( - () => { - manifest.assertIntegrity('blob:https://example.com/has-origin'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - assert.strictEqual( - manifest.assertIntegrity('blob:foo'), - true - ); - } -} -// #endregion -// #startonerror -{ - const manifest = new Manifest({ - scopes: { - 'file:///': { - integrity: true - } - }, - onerror: 'throw' - }); - assert.throws( - () => { - manifest.assertIntegrity('http://example.com'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); -} -{ - assert.throws( - () => { - new Manifest({ - scopes: { - 'file:///': { - integrity: true - } - }, - onerror: 'unknown' - }); - }, - /ERR_MANIFEST_UNKNOWN_ONERROR/ - ); -} -// #endonerror diff --git a/test/parallel/test-policy-scopes.js b/test/parallel/test-policy-scopes.js deleted file mode 100644 index 43789713cc979a..00000000000000 --- a/test/parallel/test-policy-scopes.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); - -{ - const dep = fixtures.path('policy', 'main.mjs'); - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-scopes-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 0); -} -{ - const dep = fixtures.path('policy', 'multi-deps.js'); - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-scopes-and-resources-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 0); -} diff --git a/test/pummel/test-policy-integrity-dep.js b/test/pummel/test-policy-integrity-dep.js deleted file mode 100644 index d5a23d96bc2593..00000000000000 --- a/test/pummel/test-policy-integrity-dep.js +++ /dev/null @@ -1,365 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - if (policyPath === tmpPolicyPath) { - fs.writeFileSync(tmpPolicyPath, manifestBody); - } - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - policyPath, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - process.kill(process.pid, 'SIGKILL'); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension, packageType) { - if (extension === '.js') { - return packageType === 'module' ? 'module' : 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageType: ['no-package-json', 'module', 'commonjs'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const effectivePackageType = - permutation.packageType === 'module' ? 'module' : 'commonjs'; - const parentFormat = fileExtensionFormat( - permutation.parentExtension, - effectivePackageType, - ); - const depFormat = fileExtensionFormat( - permutation.depExtension, - effectivePackageType, - ); - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - - const packageJSON = { - main: depPath, - type: permutation.packageType, - }; - if (permutation.packageType === 'no-field') { - delete packageJSON.type; - } - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - const hasParent = permutation.preloads.includes('parent'); - if (hasParent) { - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - } - - if (permutation.packageType !== 'no-package-json') { - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - } - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath: depPath, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-parent-commonjs.js b/test/pummel/test-policy-integrity-parent-commonjs.js deleted file mode 100644 index 07eee598117ba1..00000000000000 --- a/test/pummel/test-policy-integrity-parent-commonjs.js +++ /dev/null @@ -1,352 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - willDeletePolicy, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - const cliPolicy = willDeletePolicy ? tmpPolicyPath : policyPath; - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - if (cliPolicy === tmpPolicyPath) { - fs.writeFileSync(tmpPolicyPath, manifestBody); - } - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - cliPolicy, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const entryPath = parentPath; - const packageJSON = { - main: entryPath, - type: 'commonjs', - }; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath, - willDeletePolicy: false, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-parent-module.js b/test/pummel/test-policy-integrity-parent-module.js deleted file mode 100644 index a09243ea10f529..00000000000000 --- a/test/pummel/test-policy-integrity-parent-module.js +++ /dev/null @@ -1,352 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - willDeletePolicy, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - const cliPolicy = willDeletePolicy ? tmpPolicyPath : policyPath; - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - if (cliPolicy === tmpPolicyPath) { - fs.writeFileSync(tmpPolicyPath, manifestBody); - } - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - cliPolicy, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'module'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const entryPath = parentPath; - const packageJSON = { - main: entryPath, - type: 'module', - }; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath, - willDeletePolicy: false, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-parent-no-package-json.js b/test/pummel/test-policy-integrity-parent-no-package-json.js deleted file mode 100644 index a6461a9a5835c3..00000000000000 --- a/test/pummel/test-policy-integrity-parent-no-package-json.js +++ /dev/null @@ -1,324 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - willDeletePolicy, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - const cliPolicy = willDeletePolicy ? tmpPolicyPath : policyPath; - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - if (cliPolicy === tmpPolicyPath) { - fs.writeFileSync(tmpPolicyPath, manifestBody); - } - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - cliPolicy, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const entryPath = parentPath; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath, - willDeletePolicy: false, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-worker-commonjs.js b/test/pummel/test-policy-integrity-worker-commonjs.js deleted file mode 100644 index acc4298eb7b23b..00000000000000 --- a/test/pummel/test-policy-integrity-worker-commonjs.js +++ /dev/null @@ -1,375 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; -const workerSpawningBody = ` - const path = require('path'); - const { Worker } = require('worker_threads'); - if (!process.env.PARENT_FILE) { - console.error( - 'missing required PARENT_FILE env to determine worker entry point' - ); - process.exit(33); - } - if (!process.env.DELETABLE_POLICY_FILE) { - console.error( - 'missing required DELETABLE_POLICY_FILE env to check reloading' - ); - process.exit(33); - } - const w = new Worker(path.resolve(process.env.PARENT_FILE)); - w.on('exit', (status) => process.exit(status === 0 ? 0 : 1)); -`; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - - fs.writeFileSync(tmpPolicyPath, manifestBody); - - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - tmpPolicyPath, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const workerSpawnerPath = './worker-spawner.cjs'; - const packageJSON = { - main: workerSpawnerPath, - type: 'commonjs', - }; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - resources[workerSpawnerPath] = { - body: workerSpawningBody, - integrities: hash('sha256', workerSpawningBody), - }; - - - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath: workerSpawnerPath, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-worker-module.js b/test/pummel/test-policy-integrity-worker-module.js deleted file mode 100644 index 65a04841415da9..00000000000000 --- a/test/pummel/test-policy-integrity-worker-module.js +++ /dev/null @@ -1,373 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; -const workerSpawningBody = ` - const path = require('path'); - const { Worker } = require('worker_threads'); - if (!process.env.PARENT_FILE) { - console.error( - 'missing required PARENT_FILE env to determine worker entry point' - ); - process.exit(33); - } - if (!process.env.DELETABLE_POLICY_FILE) { - console.error( - 'missing required DELETABLE_POLICY_FILE env to check reloading' - ); - process.exit(33); - } - const w = new Worker(path.resolve(process.env.PARENT_FILE)); - w.on('exit', (status) => process.exit(status === 0 ? 0 : 1)); -`; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - - fs.writeFileSync(tmpPolicyPath, manifestBody); - - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - tmpPolicyPath, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'module'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const workerSpawnerPath = './worker-spawner.cjs'; - const packageJSON = { - main: workerSpawnerPath, - type: 'module', - }; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - resources[workerSpawnerPath] = { - body: workerSpawningBody, - integrities: hash('sha256', workerSpawningBody), - }; - - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath: workerSpawnerPath, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-worker-no-package-json.js b/test/pummel/test-policy-integrity-worker-no-package-json.js deleted file mode 100644 index fc90f73a03cf31..00000000000000 --- a/test/pummel/test-policy-integrity-worker-no-package-json.js +++ /dev/null @@ -1,345 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; -const workerSpawningBody = ` - const path = require('path'); - const { Worker } = require('worker_threads'); - if (!process.env.PARENT_FILE) { - console.error( - 'missing required PARENT_FILE env to determine worker entry point' - ); - process.exit(33); - } - if (!process.env.DELETABLE_POLICY_FILE) { - console.error( - 'missing required DELETABLE_POLICY_FILE env to check reloading' - ); - process.exit(33); - } - const w = new Worker(path.resolve(process.env.PARENT_FILE)); - w.on('exit', (status) => process.exit(status === 0 ? 0 : 1)); -`; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - - fs.writeFileSync(tmpPolicyPath, manifestBody); - - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - tmpPolicyPath, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const workerSpawnerPath = './worker-spawner.cjs'; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - resources[workerSpawnerPath] = { - body: workerSpawningBody, - integrities: hash('sha256', workerSpawningBody), - }; - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath: workerSpawnerPath, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/typings/internalBinding/modules.d.ts b/typings/internalBinding/modules.d.ts index 86efd6c971ae04..b9aa518abde337 100644 --- a/typings/internalBinding/modules.d.ts +++ b/typings/internalBinding/modules.d.ts @@ -20,11 +20,7 @@ export type SerializedPackageConfig = [ export interface ModulesBinding { readPackageJSON(path: string): SerializedPackageConfig | undefined; getNearestParentPackageJSON(path: string): PackageConfig | undefined - getNearestParentPackageJSONType(path: string): [ - PackageConfig['type'], - string, // package.json path - string, // raw content - ] + getNearestParentPackageJSONType(path: string): PackageConfig['type'] getPackageScopeConfig(path: string): SerializedPackageConfig | undefined getPackageJSONScripts(): string | undefined }