policy: manifest with subresource integrity checks

This enables code loaded via the module system to be checked for
integrity to ensure the code loaded matches expectations.

PR-URL: https://github.com/nodejs/node/pull/23834
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Vladimir de Turckheim <vlad2t@hotmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
Bradley Farias 2018-09-13 14:27:12 -05:00
parent 7b6e9aedaf
commit 9d5fbeb55f
16 changed files with 779 additions and 5 deletions

View File

@ -90,6 +90,13 @@ added: v8.5.0
Enable experimental ES module support and caching modules.
### `--experimental-policy`
<!-- YAML
added: TODO
-->
Use the specified file as a security policy.
### `--experimental-repl-await`
<!-- YAML
added: v10.0.0

View File

@ -1380,6 +1380,39 @@ An attempt was made to open an IPC communication channel with a synchronously
forked Node.js process. See the documentation for the [`child_process`][] module
for more information.
<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_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_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
@ -1590,6 +1623,13 @@ An attempt was made to operate on an already closed socket.
A call was made and the UDP subsystem was not running.
<a id="ERR_SRI_PARSE"></a>
### ERR_SRI_PARSE
A string was provided for a Subresource Integrity check, but was unable to be
parsed. Check the format of integrity attributes by looking at the
[Subresource Integrity specification][].
<a id="ERR_STREAM_CANNOT_PIPE"></a>
### ERR_STREAM_CANNOT_PIPE
@ -2229,7 +2269,9 @@ such as `process.stdout.on('data')`.
[domains]: domain.html
[event emitter-based]: events.html#events_class_eventemitter
[file descriptors]: https://en.wikipedia.org/wiki/File_descriptor
[policy]: policy.html
[stream-based]: stream.html
[syscall]: http://man7.org/linux/man-pages/man2/syscalls.2.html
[Subresource Integrity specification]: https://www.w3.org/TR/SRI/#the-integrity-attribute
[try-catch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
[vm]: vm.html

View File

@ -39,6 +39,7 @@
* [OS](os.html)
* [Path](path.html)
* [Performance Hooks](perf_hooks.html)
* [Policies](policy.html)
* [Process](process.html)
* [Punycode](punycode.html)
* [Query Strings](querystring.html)

104
doc/api/policy.md Normal file
View File

@ -0,0 +1,104 @@
# Policies
<!--introduced_in=TODO-->
<!-- type=misc -->
> Stability: 1 - Experimental
<!-- name=policy -->
Node.js contains experimental support for creating policies on loading code.
Policies are a security feature intended to allow guarantees
about what code Node.js is able to load. The use of policies assumes
safe practices for the policy files such as ensuring that policy
files cannot be overwritten by the Node.js application by using
file permissions.
A best practice would be to ensure that the policy manifest is read only for
the running Node.js application, and that the file cannot be changed
by the running Node.js application in any way. A typical setup would be to
create the policy file as a different user id than the one running Node.js
and granting read permissions to the user id running Node.js.
## Enabling
<!-- type=misc -->
The `--experimental-policy` flag can be used to enable features for policies
when loading modules.
Once this has been set, all modules must conform to a policy manifest file
passed to the flag:
```sh
node --experimental-policy=policy.json app.js
```
The policy manifest will be used to enforce constraints on code loaded by
Node.js.
## Features
### Error Behavior
When a policy check fails, Node.js by default will throw an error.
It is possible to change the error behavior to one of a few possibilities
by defining an "onerror" field in a policy manifest. The following values are
available to change the behavior:
* `"exit"` - will exit the process immediately.
No cleanup code will be allowed to run.
* `"log"` - will log the error at the site of the failure.
* `"throw"` (default) - will throw a JS error at the site of the failure.
```json
{
"onerror": "log",
"resources": {
"./app/checked.js": {
"integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
}
}
}
```
### Integrity Checks
Policy files must use integrity checks with Subresource Integrity strings
compatible with the browser
[integrity attribute](https://www.w3.org/TR/SRI/#the-integrity-attribute)
associated with absolute URLs.
When using `require()` all resources involved in loading are checked for
integrity if a policy manifest has been specified. If a resource does not match
the integrity listed in the manifest, an error will be thrown.
An example policy file that would allow loading a file `checked.js`:
```json
{
"resources": {
"./app/checked.js": {
"integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
}
}
}
```
Each resource listed in the policy manifest can be of one the following
formats to determine its location:
1. A [relative url string][] to a resource from the manifest such as `./resource.js`, `../resource.js`, or `/resource.js`.
2. A complete url string to a resource such as `file:///resource.js`.
When loading resources the entire URL must match including search parameters
and hash fragment. `./a.js?b` will not be used when attempting to load
`./a.js` and vice versa.
In order to generate integrity strings, a script such as
`printf "sha384-$(cat checked.js | openssl dgst -sha384 -binary | base64)"`
can be used.
[relative url string]: https://url.spec.whatwg.org/#relative-url-with-fragment-string

View File

@ -86,6 +86,9 @@ Requires Node.js to be built with
.It Fl -experimental-modules
Enable experimental ES module support and caching modules.
.
.It Fl -experimental-policy
Use the specified file as a security policy.
.
.It Fl -experimental-repl-await
Enable experimental top-level
.Sy await

View File

@ -175,6 +175,28 @@ function startup() {
mainThreadSetup.setupChildProcessIpcChannel();
}
// TODO(joyeecheung): move this down further to get better snapshotting
if (getOptionValue('[has_experimental_policy]')) {
process.emitWarning('Policies are experimental.',
'ExperimentalWarning');
const experimentalPolicy = getOptionValue('--experimental-policy');
const { pathToFileURL, URL } = NativeModule.require('url');
// URL here as it is slightly different parsing
// no bare specifiers for now
let manifestURL;
if (NativeModule.require('path').isAbsolute(experimentalPolicy)) {
manifestURL = new URL(`file:///${experimentalPolicy}`);
} else {
const cwdURL = pathToFileURL(process.cwd());
cwdURL.pathname += '/';
manifestURL = new URL(experimentalPolicy, cwdURL);
}
const fs = NativeModule.require('fs');
const src = fs.readFileSync(manifestURL, 'utf8');
NativeModule.require('internal/process/policy')
.setup(src, manifestURL.href);
}
const browserGlobals = !process._noBrowserGlobals;
if (browserGlobals) {
setupGlobalTimeouts();

View File

@ -818,6 +818,28 @@ E('ERR_IPC_CHANNEL_CLOSED', 'Channel closed', Error);
E('ERR_IPC_DISCONNECTED', 'IPC channel is already disconnected', Error);
E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe', Error);
E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks', 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 = [...realIntegrities.entries()].map(([alg, dgs]) => {
return `${alg}-${dgs}`;
}).join(' ');
msg += ` Integrities found are: ${sri}`;
} else {
msg += ' The resource was not found in the policy.';
}
return msg;
}, Error);
E('ERR_MANIFEST_INTEGRITY_MISMATCH',
'Manifest resource %s has multiple entries but integrity lists do not match',
SyntaxError);
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) => {
@ -889,6 +911,9 @@ E('ERR_SOCKET_BUFFER_SIZE',
E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data', Error);
E('ERR_SOCKET_CLOSED', 'Socket is closed', Error);
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running', Error);
E('ERR_SRI_PARSE',
'Subresource Integrity string %s had an unexpected at %d',
SyntaxError);
E('ERR_STREAM_CANNOT_PIPE', 'Cannot pipe, not readable', Error);
E('ERR_STREAM_DESTROYED', 'Cannot call %s after a stream was destroyed', Error);
E('ERR_STREAM_NULL_VALUES', 'May not write null values to stream', TypeError);

View File

@ -22,8 +22,8 @@
'use strict';
const { NativeModule } = require('internal/bootstrap/loaders');
const util = require('util');
const { pathToFileURL } = require('internal/url');
const util = require('util');
const vm = require('vm');
const assert = require('assert').ok;
const fs = require('fs');
@ -45,6 +45,9 @@ const { getOptionValue } = require('internal/options');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const experimentalModules = getOptionValue('--experimental-modules');
const manifest = getOptionValue('[has_experimental_policy]') ?
require('internal/process/policy').manifest :
null;
const {
ERR_INVALID_ARG_VALUE,
@ -168,6 +171,11 @@ function readPackage(requestPath) {
return false;
}
if (manifest) {
const jsonURL = pathToFileURL(jsonPath);
manifest.assertIntegrity(jsonURL, json);
}
try {
return packageMainCache[requestPath] = JSON.parse(json).main;
} catch (e) {
@ -676,6 +684,10 @@ function normalizeReferrerURL(referrer) {
// the file.
// Returns exception, if any.
Module.prototype._compile = function(content, filename) {
if (manifest) {
const moduleURL = pathToFileURL(filename);
manifest.assertIntegrity(moduleURL, content);
}
content = stripShebang(content);
@ -715,11 +727,14 @@ Module.prototype._compile = function(content, filename) {
var depth = requireDepth;
if (depth === 0) stat.cache = new Map();
var result;
var exports = this.exports;
var thisValue = exports;
var module = this;
if (inspectorWrapper) {
result = inspectorWrapper(compiledWrapper, this.exports, this.exports,
require, this, filename, dirname);
result = inspectorWrapper(compiledWrapper, thisValue, exports,
require, module, filename, dirname);
} else {
result = compiledWrapper.call(this.exports, this.exports, require, this,
result = compiledWrapper.call(thisValue, exports, require, module,
filename, dirname);
}
if (depth === 0) stat.cache = null;
@ -736,7 +751,13 @@ Module._extensions['.js'] = function(module, filename) {
// Native extension for .json
Module._extensions['.json'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
const content = fs.readFileSync(filename, 'utf8');
if (manifest) {
const moduleURL = pathToFileURL(filename);
manifest.assertIntegrity(moduleURL, content);
}
try {
module.exports = JSON.parse(stripBOM(content));
} catch (err) {
@ -748,6 +769,12 @@ Module._extensions['.json'] = function(module, filename) {
// Native extension for .node
Module._extensions['.node'] = function(module, filename) {
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));
};

View File

@ -0,0 +1,130 @@
'use strict';
const {
ERR_MANIFEST_ASSERT_INTEGRITY,
ERR_MANIFEST_INTEGRITY_MISMATCH,
ERR_MANIFEST_UNKNOWN_ONERROR,
} = require('internal/errors').codes;
const debug = require('util').debuglog('policy');
const SRI = require('internal/policy/sri');
const { SafeWeakMap } = require('internal/safe_globals');
const crypto = require('crypto');
const { Buffer } = require('buffer');
const { URL } = require('url');
const { createHash, timingSafeEqual } = crypto;
const HashUpdate = Function.call.bind(crypto.Hash.prototype.update);
const HashDigest = Function.call.bind(crypto.Hash.prototype.digest);
const BufferEquals = Function.call.bind(Buffer.prototype.equals);
const BufferToString = Function.call.bind(Buffer.prototype.toString);
const RegExpTest = Function.call.bind(RegExp.prototype.test);
const { entries } = Object;
const kIntegrities = new SafeWeakMap();
const kReactions = new SafeWeakMap();
const kRelativeURLStringPattern = /^\.{0,2}\//;
const { shouldAbortOnUncaughtException } = internalBinding('config');
const { abort, exit, _rawDebug } = process;
function REACTION_THROW(error) {
throw error;
}
function REACTION_EXIT(error) {
REACTION_LOG(error);
if (shouldAbortOnUncaughtException) {
abort();
}
exit(1);
}
function REACTION_LOG(error) {
_rawDebug(error.stack);
}
class Manifest {
constructor(obj, manifestURL) {
const integrities = {
__proto__: null,
};
const reactions = {
__proto__: null,
integrity: REACTION_THROW,
};
if (obj.onerror) {
const behavior = obj.onerror;
if (behavior === 'throw') {
} else if (behavior === 'exit') {
reactions.integrity = REACTION_EXIT;
} else if (behavior === 'log') {
reactions.integrity = REACTION_LOG;
} else {
throw new ERR_MANIFEST_UNKNOWN_ONERROR(behavior);
}
}
kReactions.set(this, Object.freeze(reactions));
const manifestEntries = entries(obj.resources);
for (var i = 0; i < manifestEntries.length; i++) {
let url = manifestEntries[i][0];
const integrity = manifestEntries[i][1].integrity;
if (integrity != null) {
debug(`Manifest contains integrity for url ${url}`);
if (RegExpTest(kRelativeURLStringPattern, url)) {
url = new URL(url, manifestURL).href;
}
const sri = Object.freeze(SRI.parse(integrity));
if (url in integrities) {
const old = integrities[url];
let mismatch = false;
if (old.length !== sri.length) {
mismatch = true;
} else {
compare:
for (var sriI = 0; sriI < sri.length; sriI++) {
for (var oldI = 0; oldI < old.length; oldI++) {
if (sri[sriI].algorithm === old[oldI].algorithm &&
BufferEquals(sri[sriI].value, old[oldI].value) &&
sri[sriI].options === old[oldI].options) {
continue compare;
}
}
mismatch = true;
break compare;
}
}
if (mismatch) {
throw new ERR_MANIFEST_INTEGRITY_MISMATCH(url);
}
}
integrities[url] = sri;
}
}
Object.freeze(integrities);
kIntegrities.set(this, integrities);
Object.freeze(this);
}
assertIntegrity(url, content) {
debug(`Checking integrity of ${url}`);
const integrities = kIntegrities.get(this);
const realIntegrities = new Map();
if (integrities && url in integrities) {
const integrityEntries = integrities[url];
// Avoid clobbered Symbol.iterator
for (var i = 0; i < integrityEntries.length; i++) {
const {
algorithm,
value: expected
} = integrityEntries[i];
const hash = createHash(algorithm);
HashUpdate(hash, content);
const digest = HashDigest(hash);
if (digest.length === expected.length &&
timingSafeEqual(digest, expected)) {
return true;
}
realIntegrities.set(algorithm, BufferToString(digest, 'base64'));
}
}
const error = new ERR_MANIFEST_ASSERT_INTEGRITY(url, realIntegrities);
kReactions.get(this).integrity(error);
}
}
// Lock everything down to avoid problems even if reference is leaked somehow
Object.setPrototypeOf(Manifest, null);
Object.setPrototypeOf(Manifest.prototype, null);
Object.freeze(Manifest);
Object.freeze(Manifest.prototype);
module.exports = Object.freeze({ Manifest });

View File

@ -0,0 +1,68 @@
'use strict';
// Value of https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute
// Returns [{algorithm, value (in base64 string), options,}]
const {
ERR_SRI_PARSE
} = require('internal/errors').codes;
const kWSP = '[\\x20\\x09]';
const kVCHAR = '[\\x21-\\x7E]';
const kHASH_ALGO = 'sha256|sha384|sha512';
// Base64
const kHASH_VALUE = '[A-Za-z0-9+/]+[=]{0,2}';
const kHASH_EXPRESSION = `(${kHASH_ALGO})-(${kHASH_VALUE})`;
const kOPTION_EXPRESSION = `(${kVCHAR}*)`;
const kHASH_WITH_OPTIONS = `${kHASH_EXPRESSION}(?:[?](${kOPTION_EXPRESSION}))?`;
const kSRIPattern = new RegExp(`(${kWSP}*)(?:${kHASH_WITH_OPTIONS})`, 'g');
const { freeze } = Object;
Object.seal(kSRIPattern);
const kAllWSP = new RegExp(`^${kWSP}*$`);
Object.seal(kAllWSP);
const RegExpExec = Function.call.bind(RegExp.prototype.exec);
const RegExpTest = Function.call.bind(RegExp.prototype.test);
const StringSlice = Function.call.bind(String.prototype.slice);
const {
Buffer: {
from: BufferFrom
}
} = require('buffer');
const { defineProperty } = Object;
const parse = (str) => {
kSRIPattern.lastIndex = 0;
let prevIndex = 0;
let match = RegExpExec(kSRIPattern, str);
const entries = [];
while (match) {
if (match.index !== prevIndex) {
throw new ERR_SRI_PARSE(str, prevIndex);
}
if (entries.length > 0) {
if (match[1] === '') {
throw new ERR_SRI_PARSE(str, prevIndex);
}
}
// Avoid setters being fired
defineProperty(entries, entries.length, {
enumerable: true,
configurable: true,
value: freeze({
__proto__: null,
algorithm: match[2],
value: BufferFrom(match[3], 'base64'),
options: match[4] === undefined ? null : match[4],
})
});
prevIndex = prevIndex + match[0].length;
match = RegExpExec(kSRIPattern, str);
}
if (prevIndex !== str.length) {
if (!RegExpTest(kAllWSP, StringSlice(str, prevIndex))) {
throw new ERR_SRI_PARSE(str, prevIndex);
}
}
return entries;
};
module.exports = {
parse,
};

View File

@ -0,0 +1,33 @@
'use strict';
const {
ERR_MANIFEST_TDZ,
} = require('internal/errors').codes;
const { Manifest } = require('internal/policy/manifest');
let manifest;
module.exports = Object.freeze({
__proto__: null,
setup(src, url) {
if (src === null) {
manifest = null;
return;
}
const json = JSON.parse(src, (_, o) => {
if (o && typeof o === 'object') {
Reflect.setPrototypeOf(o, null);
Object.freeze(o);
}
return o;
});
manifest = new Manifest(json, url);
},
get manifest() {
if (typeof manifest === 'undefined') {
throw new ERR_MANIFEST_TDZ();
}
return manifest;
},
assertIntegrity(moduleURL, content) {
this.manifest.matchesIntegrity(moduleURL, content);
}
});

View File

@ -20,5 +20,6 @@ const makeSafe = (unsafe, safe) => {
};
exports.SafeMap = makeSafe(Map, class SafeMap extends Map {});
exports.SafeWeakMap = makeSafe(WeakMap, class SafeWeakMap extends WeakMap {});
exports.SafeSet = makeSafe(Set, class SafeSet extends Set {});
exports.SafePromise = makeSafe(Promise, class SafePromise extends Promise {});

View File

@ -142,6 +142,8 @@
'lib/internal/safe_globals.js',
'lib/internal/net.js',
'lib/internal/options.js',
'lib/internal/policy/sri.js',
'lib/internal/policy/manifest.js',
'lib/internal/print_help.js',
'lib/internal/priority_queue.js',
'lib/internal/process/esm_loader.js',
@ -149,6 +151,7 @@
'lib/internal/process/main_thread_only.js',
'lib/internal/process/next_tick.js',
'lib/internal/process/per_thread.js',
'lib/internal/process/policy.js',
'lib/internal/process/promises.js',
'lib/internal/process/stdio.js',
'lib/internal/process/warning.js',

View File

@ -101,6 +101,15 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"experimental ES Module support and caching modules",
&EnvironmentOptions::experimental_modules,
kAllowedInEnvironment);
AddOption("[has_experimental_policy]",
"",
&EnvironmentOptions::has_experimental_policy);
AddOption("--experimental-policy",
"use the specified file as a "
"security policy",
&EnvironmentOptions::experimental_policy,
kAllowedInEnvironment);
Implies("--experimental-policy", "[has_experimental_policy]");
AddOption("--experimental-repl-await",
"experimental await keyword support in REPL",
&EnvironmentOptions::experimental_repl_await,

View File

@ -94,6 +94,8 @@ class EnvironmentOptions : public Options {
public:
bool abort_on_uncaught_exception = false;
bool experimental_modules = false;
std::string experimental_policy;
bool has_experimental_policy;
bool experimental_repl_await = false;
bool experimental_vm_modules = false;
bool expose_internals = false;

View File

@ -0,0 +1,297 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
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 policyFilepath = path.join(tmpdir.path, 'policy');
const packageFilepath = path.join(tmpdir.path, 'package.json');
const packageURL = pathToFileURL(packageFilepath);
const packageBody = '{"main": "dep.js"}';
const policyToPackageRelativeURLString = `./${
path.relative(path.dirname(policyFilepath), packageFilepath)
}`;
const parentFilepath = path.join(tmpdir.path, 'parent.js');
const parentURL = pathToFileURL(parentFilepath);
const parentBody = 'require(\'./dep.js\')';
const depFilepath = path.join(tmpdir.path, 'dep.js');
const depURL = pathToFileURL(depFilepath);
const depBody = '';
const policyToDepRelativeURLString = `./${
path.relative(path.dirname(policyFilepath), depFilepath)
}`;
fs.writeFileSync(parentFilepath, parentBody);
fs.writeFileSync(depFilepath, depBody);
const tmpdirURL = pathToFileURL(tmpdir.path);
if (!tmpdirURL.pathname.endsWith('/')) {
tmpdirURL.pathname += '/';
}
function test({
shouldFail = false,
entry,
onerror,
resources = {}
}) {
const manifest = {
onerror,
resources: {}
};
for (const [url, { body, match }] of Object.entries(resources)) {
manifest.resources[url] = {
integrity: `sha256-${hash('sha256', match ? body : body + '\n')}`
};
fs.writeFileSync(new URL(url, tmpdirURL.href), body);
}
fs.writeFileSync(policyFilepath, JSON.stringify(manifest, null, 2));
const { status } = spawnSync(process.execPath, [
'--experimental-policy', policyFilepath, entry
]);
if (shouldFail) {
assert.notStrictEqual(status, 0);
} else {
assert.strictEqual(status, 0);
}
}
const { status } = spawnSync(process.execPath, [
'--experimental-policy', policyFilepath,
'--experimental-policy', policyFilepath
], {
stdio: 'pipe'
});
assert.notStrictEqual(status, 0, 'Should not allow multiple policies');
test({
shouldFail: true,
entry: parentFilepath,
resources: {
}
});
test({
shouldFail: false,
entry: parentFilepath,
onerror: 'log',
});
test({
shouldFail: true,
entry: parentFilepath,
onerror: 'exit',
});
test({
shouldFail: true,
entry: parentFilepath,
onerror: 'throw',
});
test({
shouldFail: true,
entry: parentFilepath,
onerror: 'unknown-onerror-value',
});
test({
shouldFail: true,
entry: path.dirname(packageFilepath),
resources: {
}
});
test({
shouldFail: true,
entry: path.dirname(packageFilepath),
resources: {
[depURL]: {
body: depBody,
match: true,
}
}
});
test({
shouldFail: false,
entry: path.dirname(packageFilepath),
onerror: 'log',
resources: {
[packageURL]: {
body: packageBody,
match: false,
},
[depURL]: {
body: depBody,
match: true,
}
}
});
test({
shouldFail: true,
entry: path.dirname(packageFilepath),
resources: {
[packageURL]: {
body: packageBody,
match: false,
},
[depURL]: {
body: depBody,
match: true,
}
}
});
test({
shouldFail: true,
entry: path.dirname(packageFilepath),
resources: {
[packageURL]: {
body: packageBody,
match: true,
},
[depURL]: {
body: depBody,
match: false,
}
}
});
test({
shouldFail: false,
entry: path.dirname(packageFilepath),
resources: {
[packageURL]: {
body: packageBody,
match: true,
},
[depURL]: {
body: depBody,
match: true,
}
}
});
test({
shouldFail: false,
entry: parentFilepath,
resources: {
[parentURL]: {
body: parentBody,
match: true,
},
[depURL]: {
body: depBody,
match: true,
}
}
});
test({
shouldFail: true,
entry: parentFilepath,
resources: {
[parentURL]: {
body: parentBody,
match: false,
},
[depURL]: {
body: depBody,
match: true,
}
}
});
test({
shouldFail: true,
entry: parentFilepath,
resources: {
[parentURL]: {
body: parentBody,
match: true,
},
[depURL]: {
body: depBody,
match: false,
}
}
});
test({
shouldFail: true,
entry: parentFilepath,
resources: {
[parentURL]: {
body: parentBody,
match: true,
}
}
});
test({
shouldFail: false,
entry: depFilepath,
resources: {
[depURL]: {
body: depBody,
match: true,
}
}
});
test({
shouldFail: false,
entry: depFilepath,
resources: {
[policyToDepRelativeURLString]: {
body: depBody,
match: true,
}
}
});
test({
shouldFail: true,
entry: depFilepath,
resources: {
[policyToDepRelativeURLString]: {
body: depBody,
match: false,
}
}
});
test({
shouldFail: false,
entry: depFilepath,
resources: {
[policyToDepRelativeURLString]: {
body: depBody,
match: true,
},
[depURL]: {
body: depBody,
match: true,
}
}
});
test({
shouldFail: true,
entry: depFilepath,
resources: {
[policyToPackageRelativeURLString]: {
body: packageBody,
match: true,
},
[packageURL]: {
body: packageBody,
match: true,
},
[depURL]: {
body: depBody,
match: false,
}
}
});