mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
lib,src: remove --experimental-policy
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> PR-URL: https://github.com/nodejs/node/pull/52583 Refs: https://github.com/nodejs/node/issues/52575 Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
This commit is contained in:
parent
9a1df15ee7
commit
951af83033
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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`
|
||||
|
||||
<!-- YAML
|
||||
added: v11.8.0
|
||||
-->
|
||||
|
||||
> Stability: 0 - Deprecated: Will be removed shortly.
|
||||
|
||||
Use the specified file as a security policy.
|
||||
|
||||
### `--experimental-require-module`
|
||||
|
||||
<!-- YAML
|
||||
|
|
@ -1508,18 +1498,6 @@ unless either the `--pending-deprecation` command-line flag, or the
|
|||
are used to provide a kind of selective "early warning" mechanism that
|
||||
developers may leverage to detect deprecated API usage.
|
||||
|
||||
### `--policy-integrity=sri`
|
||||
|
||||
<!-- YAML
|
||||
added: v12.7.0
|
||||
-->
|
||||
|
||||
> 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`
|
||||
|
||||
<!-- YAML
|
||||
|
|
@ -2622,7 +2600,6 @@ one is included in the list below.
|
|||
* `--experimental-modules`
|
||||
* `--experimental-network-imports`
|
||||
* `--experimental-permission`
|
||||
* `--experimental-policy`
|
||||
* `--experimental-print-required-tla`
|
||||
* `--experimental-require-module`
|
||||
* `--experimental-shadow-realm`
|
||||
|
|
@ -2664,7 +2641,6 @@ one is included in the list below.
|
|||
* `--openssl-legacy-provider`
|
||||
* `--openssl-shared-config`
|
||||
* `--pending-deprecation`
|
||||
* `--policy-integrity`
|
||||
* `--preserve-symlinks-main`
|
||||
* `--preserve-symlinks`
|
||||
* `--prof-process`
|
||||
|
|
@ -3136,7 +3112,6 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
|
|||
[ScriptCoverage]: https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ScriptCoverage
|
||||
[ShadowRealm]: https://github.com/tc39/proposal-shadowrealm
|
||||
[Source Map]: https://sourcemaps.info/spec.html
|
||||
[Subresource Integrity]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
|
||||
[V8 JavaScript code coverage]: https://v8project.blogspot.com/2017/12/javascript-code-coverage.html
|
||||
[V8 code cache]: https://v8.dev/blog/code-caching-for-devs
|
||||
[`"type"`]: packages.md#type
|
||||
|
|
|
|||
|
|
@ -2354,7 +2354,7 @@ Type: Documentation-only (supports [`--pending-deprecation`][])
|
|||
`process.binding()` is for use by Node.js internal code only.
|
||||
|
||||
While `process.binding()` has not reached End-of-Life status in general, it is
|
||||
unavailable when [policies][] or the [permission model][] are enabled.
|
||||
unavailable when the [permission model][] is enabled.
|
||||
|
||||
### DEP0112: `dgram` private APIs
|
||||
|
||||
|
|
@ -3836,7 +3836,6 @@ is deprecated to better align with recommendations per [NIST SP 800-38D][].
|
|||
[legacy URL API]: url.md#legacy-url-api
|
||||
[legacy `urlObject`]: url.md#legacy-urlobject
|
||||
[permission model]: permissions.md#permission-model
|
||||
[policies]: permissions.md#policies
|
||||
[static methods of `crypto.Certificate()`]: crypto.md#class-certificate
|
||||
[subpath exports]: packages.md#subpath-exports
|
||||
[subpath imports]: packages.md#subpath-imports
|
||||
|
|
|
|||
|
|
@ -2159,68 +2159,6 @@ added:
|
|||
An ESM loader hook returned without calling `next()` and without explicitly
|
||||
signaling a short circuit.
|
||||
|
||||
<a id="ERR_MANIFEST_ASSERT_INTEGRITY"></a>
|
||||
|
||||
### `ERR_MANIFEST_ASSERT_INTEGRITY`
|
||||
|
||||
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.
|
||||
|
||||
<a id="ERR_MANIFEST_DEPENDENCY_MISSING"></a>
|
||||
|
||||
### `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.
|
||||
|
||||
<a id="ERR_MANIFEST_INTEGRITY_MISMATCH"></a>
|
||||
|
||||
### `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.
|
||||
|
||||
<a id="ERR_MANIFEST_INVALID_RESOURCE_FIELD"></a>
|
||||
|
||||
### `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.
|
||||
|
||||
<a id="ERR_MANIFEST_INVALID_SPECIFIER"></a>
|
||||
|
||||
### `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.
|
||||
|
||||
<a id="ERR_MANIFEST_PARSE_POLICY"></a>
|
||||
|
||||
### `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.
|
||||
|
||||
<a id="ERR_MANIFEST_TDZ"></a>
|
||||
|
||||
### `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.
|
||||
|
||||
<a id="ERR_MANIFEST_UNKNOWN_ONERROR"></a>
|
||||
|
||||
### `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.
|
||||
|
||||
<a id="ERR_MEMORY_ALLOCATION_FAILED"></a>
|
||||
|
||||
### `ERR_MEMORY_ALLOCATION_FAILED`
|
||||
|
|
@ -3436,6 +3374,100 @@ removed: v21.1.0
|
|||
|
||||
An import attribute is not supported by this version of Node.js.
|
||||
|
||||
<a id="ERR_MANIFEST_ASSERT_INTEGRITY"></a>
|
||||
|
||||
### `ERR_MANIFEST_ASSERT_INTEGRITY`
|
||||
|
||||
<!-- YAML
|
||||
removed: REPLACEME
|
||||
-->
|
||||
|
||||
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.
|
||||
|
||||
<a id="ERR_MANIFEST_DEPENDENCY_MISSING"></a>
|
||||
|
||||
### `ERR_MANIFEST_DEPENDENCY_MISSING`
|
||||
|
||||
<!-- YAML
|
||||
removed: REPLACEME
|
||||
-->
|
||||
|
||||
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.
|
||||
|
||||
<a id="ERR_MANIFEST_INTEGRITY_MISMATCH"></a>
|
||||
|
||||
### `ERR_MANIFEST_INTEGRITY_MISMATCH`
|
||||
|
||||
<!-- YAML
|
||||
removed: REPLACEME
|
||||
-->
|
||||
|
||||
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.
|
||||
|
||||
<a id="ERR_MANIFEST_INVALID_RESOURCE_FIELD"></a>
|
||||
|
||||
### `ERR_MANIFEST_INVALID_RESOURCE_FIELD`
|
||||
|
||||
<!-- YAML
|
||||
removed: REPLACEME
|
||||
-->
|
||||
|
||||
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.
|
||||
|
||||
<a id="ERR_MANIFEST_INVALID_SPECIFIER"></a>
|
||||
|
||||
### `ERR_MANIFEST_INVALID_SPECIFIER`
|
||||
|
||||
<!-- YAML
|
||||
removed: REPLACEME
|
||||
-->
|
||||
|
||||
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.
|
||||
|
||||
<a id="ERR_MANIFEST_PARSE_POLICY"></a>
|
||||
|
||||
### `ERR_MANIFEST_PARSE_POLICY`
|
||||
|
||||
<!-- YAML
|
||||
removed: REPLACEME
|
||||
-->
|
||||
|
||||
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.
|
||||
|
||||
<a id="ERR_MANIFEST_TDZ"></a>
|
||||
|
||||
### `ERR_MANIFEST_TDZ`
|
||||
|
||||
<!-- YAML
|
||||
removed: REPLACEME
|
||||
-->
|
||||
|
||||
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.
|
||||
|
||||
<a id="ERR_MANIFEST_UNKNOWN_ONERROR"></a>
|
||||
|
||||
### `ERR_MANIFEST_UNKNOWN_ONERROR`
|
||||
|
||||
<!-- YAML
|
||||
removed: REPLACEME
|
||||
-->
|
||||
|
||||
A policy manifest was loaded, but had an unknown value for its "onerror"
|
||||
behavior. See the documentation for policy manifests for more information.
|
||||
|
||||
<a id="ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST"></a>
|
||||
|
||||
### `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
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
# Policies
|
||||
|
||||
<!--introduced_in=v11.8.0-->
|
||||
|
||||
<!-- type=misc -->
|
||||
|
||||
> Stability: 1 - Experimental
|
||||
|
||||
The former Policies documentation is now at [Permissions documentation][].
|
||||
|
||||
[Permissions documentation]: permissions.md#policies
|
||||
|
|
@ -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.
|
||||
.
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<string, Module>} */
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<import('internal/policy/manifest.js').Manifest['getDependencyMapper']>} 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string>} 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<string, string>}
|
||||
*/
|
||||
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<string | null | undefined, DependencyMapperInstance>} 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<string, string> | typeof kFallThrough} DependencyMap
|
||||
* @typedef {Array<[string, [string, string]]>} PatternDependencyMap
|
||||
* @typedef {Record<string, string> | null | true} JSONDependencyMap
|
||||
*/
|
||||
/**
|
||||
* @typedef {Map<string, any>} 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<string, DependencyMapperInstance>}
|
||||
*
|
||||
* 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<string, Integrity>}
|
||||
*
|
||||
* 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<string, boolean | null | typeof kCascade>}
|
||||
*
|
||||
* 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<string, URL>}
|
||||
*/
|
||||
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<URL>}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<CompileCacheHandler>(this);
|
||||
if (handler->InitializeDirectory(this, dir_from_env)) {
|
||||
compile_cache_handler_ = std::move(handler);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -76,23 +76,21 @@ void BindingData::Deserialize(v8::Local<v8::Context> context,
|
|||
}
|
||||
|
||||
Local<Array> 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<Primitive> {
|
||||
return String::NewFromUtf8(
|
||||
isolate, input.data(), NewStringType::kNormal, input.size())
|
||||
.ToLocalChecked();
|
||||
};
|
||||
Local<Value> values[7] = {
|
||||
Local<Value> 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<Value> 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> value =
|
||||
ToV8Value(realm->context(), package_json->type).ToLocalChecked();
|
||||
args.GetReturnValue().Set(value);
|
||||
}
|
||||
|
||||
void BindingData::GetPackageScopeConfig(
|
||||
|
|
|
|||
|
|
@ -107,14 +107,6 @@ void PerIsolateOptions::CheckOptions(std::vector<std::string>* errors,
|
|||
|
||||
void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors,
|
||||
std::vector<std::string>* 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,
|
||||
|
|
|
|||
|
|
@ -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<std::string> allow_fs_read;
|
||||
std::vector<std::string> allow_fs_write;
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
require('../common');
|
||||
|
||||
const runBenchmark = require('../common/benchmark');
|
||||
|
||||
runBenchmark('policy', [
|
||||
'n=1',
|
||||
]);
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
const os = module.constructor.createRequire('file:///os-access-module.js')('os')
|
||||
os.cpus()
|
||||
9
test/fixtures/policy-manifest/invalid.json
vendored
9
test/fixtures/policy-manifest/invalid.json
vendored
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"./fhqwhgads.js": {
|
||||
"dependencies": {
|
||||
"**": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
const m = new require.main.constructor();
|
||||
m.require('./invalid-module')
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
const m = new require.main.constructor();
|
||||
require.extensions['.js'](m, './invalid-module')
|
||||
|
|
@ -1 +0,0 @@
|
|||
process.mainModule.require('os').cpus();
|
||||
|
|
@ -1 +0,0 @@
|
|||
process.mainModule.__proto__.require("os")
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"./createRequire-bypass.js": {
|
||||
"integrity": true
|
||||
},
|
||||
"/os-access-module.js": {
|
||||
"integrity": true,
|
||||
"dependencies": {
|
||||
"os": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
module.constructor._load('node:child_process');
|
||||
|
|
@ -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')
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"onerror": "exit",
|
||||
"scopes": {
|
||||
"file:": {
|
||||
"integrity": true,
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
test/fixtures/policy/bad-main.mjs
vendored
1
test/fixtures/policy/bad-main.mjs
vendored
|
|
@ -1 +0,0 @@
|
|||
import {doesNotExist} from './dep.js';
|
||||
5
test/fixtures/policy/canonicalize.mjs
vendored
5
test/fixtures/policy/canonicalize.mjs
vendored
|
|
@ -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);
|
||||
|
|
@ -1 +0,0 @@
|
|||
*.js text eol=lf
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
// No code.
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
require('crypto').DEFAULT_ENCODING = process.env.DEFAULT_ENCODING;
|
||||
require('./dep.js');
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"./parent.js": {
|
||||
"integrity": "sha384-j4pMdq83q5Bq9+idcHuGKzi89FrYm1PhZYrEw3irbNob6g4i3vKBjfYiRNYwmoGr",
|
||||
"dependencies": {
|
||||
"crypto": true,
|
||||
"./dep.js": true
|
||||
}
|
||||
},
|
||||
"./dep.js": {
|
||||
"integrity": "sha384-VU7GIrTix/HFLhUb4yqsV4n1xXqjPcWw6kLvjuKXtR1+9nmufJu5vZLajBs8brIW"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
*.js text eol=lf
|
||||
|
|
@ -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');
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
console.log(require('fs').readFileSync('/etc/passwd').length);
|
||||
7
test/fixtures/policy/dep-policy.json
vendored
7
test/fixtures/policy/dep-policy.json
vendored
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"./dep.js": {
|
||||
"integrity": "sha512-7CMcc2oytFfMnGQaXbJk84gYWF2J7p/fmWPW7dsnJyniD+vgxtK9VAZ/22UxFOA4q5d27RoGLxSqNZ/nGCJkMw== sha512-scgN9Td0bGMlGH2lUHvEeHtz92Hx6AO+sYhU3WRI6bn3jEUCXbXJs68nOOsGzRWR7a2tbqGoETnOCpHHf1Njhw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
2
test/fixtures/policy/dep.js
vendored
2
test/fixtures/policy/dep.js
vendored
|
|
@ -1,2 +0,0 @@
|
|||
'use strict';
|
||||
module.exports = 'The Secret Ingredient';
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"../parent.js": {
|
||||
"integrity": true,
|
||||
"dependencies": {}
|
||||
},
|
||||
"../dep.js": {
|
||||
"integrity": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"../bad-main.mjs": {
|
||||
"integrity": true,
|
||||
"dependencies": true
|
||||
},
|
||||
"../dep.js": {
|
||||
"integrity": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"dependencies": true,
|
||||
"resources": {
|
||||
"../parent.js": {
|
||||
"cascade": true,
|
||||
"integrity": true
|
||||
},
|
||||
"../dep.js": {
|
||||
"cascade": true,
|
||||
"integrity": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"../parent.js": {
|
||||
"integrity": true
|
||||
},
|
||||
"../dep.js": {
|
||||
"integrity": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"../parent.js": {
|
||||
"integrity": true,
|
||||
"dependencies": {
|
||||
"../dep.js": "node:util"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"../parent.js": {
|
||||
"integrity": true,
|
||||
"dependencies": {
|
||||
"../dep.js": "../dep.js"
|
||||
}
|
||||
},
|
||||
"../dep.js": {
|
||||
"integrity": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"../parent.js": {
|
||||
"integrity": true,
|
||||
"dependencies": {
|
||||
"../dep.js": "node:404"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"../multi-deps.js": {
|
||||
"integrity": true,
|
||||
"cascade": true
|
||||
}
|
||||
},
|
||||
"scopes": {
|
||||
"../": {
|
||||
"integrity": true,
|
||||
"dependencies": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"scopes": {
|
||||
"../": {
|
||||
"integrity": true,
|
||||
"dependencies": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"scopes": {
|
||||
"file:": {
|
||||
"integrity": true,
|
||||
"cascade": true,
|
||||
"dependencies": {
|
||||
"fs": "node:fs",
|
||||
"../dep.js": "node:fs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"../parent.js": {
|
||||
"integrity": true,
|
||||
"dependencies": true
|
||||
},
|
||||
"../dep.js": {
|
||||
"integrity": true
|
||||
}
|
||||
}
|
||||
}
|
||||
2
test/fixtures/policy/main.mjs
vendored
2
test/fixtures/policy/main.mjs
vendored
|
|
@ -1,2 +0,0 @@
|
|||
'use strict';
|
||||
export default 'main.mjs';
|
||||
3
test/fixtures/policy/multi-deps.js
vendored
3
test/fixtures/policy/multi-deps.js
vendored
|
|
@ -1,3 +0,0 @@
|
|||
'use strict';
|
||||
require('fs');
|
||||
require('process');
|
||||
3
test/fixtures/policy/parent.js
vendored
3
test/fixtures/policy/parent.js
vendored
|
|
@ -1,3 +0,0 @@
|
|||
'use strict';
|
||||
// Included in parent-policy.json
|
||||
require('./dep.js');
|
||||
10
test/fixtures/policy/process-binding/app.js
vendored
10
test/fixtures/policy/process-binding/app.js
vendored
|
|
@ -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'
|
||||
});
|
||||
10
test/fixtures/policy/process-binding/policy.json
vendored
10
test/fixtures/policy/process-binding/policy.json
vendored
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"resources": {
|
||||
"./app.js": {
|
||||
"integrity": true,
|
||||
"dependencies": {
|
||||
"assert": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "binding",
|
||||
"sources": [ "binding.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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)}`,
|
||||
}]);
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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'));
|
||||
|
|
@ -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')
|
||||
);
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
|
|
@ -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/);
|
||||
}
|
||||
|
|
@ -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'
|
||||
}
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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<string, Array<string | string[] | boolean>>} 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);
|
||||
}
|
||||
|
|
@ -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<string, Array<string | string[] | boolean>>} 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);
|
||||
}
|
||||
|
|
@ -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<string, Array<string | string[] | boolean>>} 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);
|
||||
}
|
||||
|
|
@ -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<string, Array<string | string[] | boolean>>} 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);
|
||||
}
|
||||
|
|
@ -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<string, Array<string | string[] | boolean>>} 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);
|
||||
}
|
||||
|
|
@ -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<string, Array<string | string[] | boolean>>} 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);
|
||||
}
|
||||
|
|
@ -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<string, Array<string | string[] | boolean>>} 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);
|
||||
}
|
||||
6
typings/internalBinding/modules.d.ts
vendored
6
typings/internalBinding/modules.d.ts
vendored
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user