Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Policy: add scopes to ease usability #34552

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions doc/api/policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,93 @@ module.exports = function fn(...args) {
};
```

### Scopes

Use the `"scopes"` field of a manifest to set configuration for many resources
at once. The `"scopes"` field works by matching resources by their segments.
If a scope or resource includes `"cascade": true` unknown specifiers will
be searched for in their containing scope. The containing scope for cascading
is found by recursively reducing the resource URL by removing segments for
[special schemes][], keeping trailing `"/"` suffixes and removing the query and
hash fragment. This leads to the eventual reduction of the URL to its origin.
If the URL is non-special the scope will be located by the URL's origin. If no
scope is found for the origin or in the case of opaque origins, a protocol
string can be used as a scope.

Note, `blob:` URLs adopt their origin from the path they contain, and so a scope
of `"blob:https://nodejs.org"` will have no effect since no URL can have an
origin of `blob:https://nodejs.org`; URLs starting with
`blob:https://nodejs.org/` will use `https://nodejs.org` for its origin and
thus `https:` for its protocol scope. For opaque origin `blob:` URLs they will
have `blob:` for their protocol scope since they do not adopt origins.

#### Integrity Using Scopes

Setting an integrity to `true` on a scope will set the integrity for any
resource not found in the manifest to `true`.

Setting an integrity to `null` on a scope will set the integrity for any
resource not found in the manifest to fail matching.

Not including an integrity is the same as setting the integrity to `null`.

`"cascade"` for integrity checks will be ignored if `"integrity"` is explicitly
set.

The following example allows loading any file:

```json
{
"scopes": {
"file:": {
"integrity": true
}
}
}
```

#### Dependency Redirection Using Scopes

The following example, would allow access to `fs` for all resources within
`./app/`:

```json
{
"resources": {
"./app/checked.js": {
"cascade": true,
"integrity": true
}
},
"scopes": {
"./app/": {
"dependencies": {
"fs": true
}
}
}
}
```

The following example, would allow access to `fs` for all `data:` resources:

```json
{
"resources": {
"data:text/javascript,import('fs');": {
"cascade": true,
"integrity": true
}
},
"scopes": {
"data:": {
"dependencies": {
"fs": true
}
}
}
}
```

[relative url string]: https://url.spec.whatwg.org/#relative-url-with-fragment-string
[special schemes]: https://url.spec.whatwg.org/#special-scheme
21 changes: 11 additions & 10 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ const {
SafeWeakMap,
String,
StringPrototypeEndsWith,
StringPrototypeIndexOf,
StringPrototypeLastIndexOf,
StringPrototypeIndexOf,
StringPrototypeMatch,
StringPrototypeSlice,
StringPrototypeStartsWith,
Expand Down Expand Up @@ -82,8 +82,9 @@ const {
const { getOptionValue } = require('internal/options');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const manifest = getOptionValue('--experimental-policy') ?
require('internal/process/policy').manifest :
// Do not eagerly grab .manifest, it may be in TDZ
const policy = getOptionValue('--experimental-policy') ?
require('internal/process/policy') :
null;
const { compileFunction } = internalBinding('contextify');

Expand Down Expand Up @@ -1043,10 +1044,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
Module.prototype._compile = function(content, filename) {
let moduleURL;
let redirects;
if (manifest) {
if (policy?.manifest) {
moduleURL = pathToFileURL(filename);
redirects = manifest.getRedirector(moduleURL);
manifest.assertIntegrity(moduleURL, content);
redirects = policy.manifest.getDependencyMapper(moduleURL);
policy.manifest.assertIntegrity(moduleURL, content);
}

maybeCacheSourceMap(filename, content, this);
Expand Down Expand Up @@ -1115,9 +1116,9 @@ Module._extensions['.js'] = function(module, filename) {
Module._extensions['.json'] = function(module, filename) {
const content = fs.readFileSync(filename, 'utf8');

if (manifest) {
if (policy?.manifest) {
const moduleURL = pathToFileURL(filename);
manifest.assertIntegrity(moduleURL, content);
policy.manifest.assertIntegrity(moduleURL, content);
}

try {
Expand All @@ -1131,10 +1132,10 @@ Module._extensions['.json'] = function(module, filename) {

// Native extension for .node
Module._extensions['.node'] = function(module, filename) {
if (manifest) {
if (policy?.manifest) {
const content = fs.readFileSync(filename);
const moduleURL = pathToFileURL(filename);
manifest.assertIntegrity(moduleURL, content);
policy.manifest.assertIntegrity(moduleURL, content);
}
// Be aware this doesn't use `content`
return process.dlopen(module, path.toNamespacedPath(filename));
Expand Down
9 changes: 5 additions & 4 deletions lib/internal/modules/esm/get_source.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict';

const { getOptionValue } = require('internal/options');
const manifest = getOptionValue('--experimental-policy') ?
require('internal/process/policy').manifest :
// Do not eagerly grab .manifest, it may be in TDZ
const policy = getOptionValue('--experimental-policy') ?
require('internal/process/policy') :
null;

const { Buffer } = require('buffer');
Expand Down Expand Up @@ -33,8 +34,8 @@ async function defaultGetSource(url, { format } = {}, defaultGetSource) {
} else {
throw new ERR_INVALID_URL_SCHEME(['file', 'data']);
}
if (manifest) {
manifest.assertIntegrity(parsed, source);
if (policy?.manifest) {
policy.manifest.assertIntegrity(parsed, source);
}
return { source };
}
Expand Down
9 changes: 5 additions & 4 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ const {
Stats,
} = require('fs');
const { getOptionValue } = require('internal/options');
const manifest = getOptionValue('--experimental-policy') ?
require('internal/process/policy').manifest :
// Do not eagerly grab .manifest, it may be in TDZ
const policy = getOptionValue('--experimental-policy') ?
require('internal/process/policy') :
null;
const { sep, relative } = require('path');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
Expand Down Expand Up @@ -714,8 +715,8 @@ function resolveAsCommonJS(specifier, parentURL) {

function defaultResolve(specifier, context = {}, defaultResolveUnused) {
let { parentURL, conditions } = context;
if (manifest) {
const redirects = manifest.getRedirector(parentURL);
if (parentURL && policy?.manifest) {
const redirects = policy.manifest.getDependencyMapper(parentURL);
if (redirects) {
const { resolve, reaction } = redirects;
const destination = resolve(specifier, new SafeSet(conditions));
Expand Down
12 changes: 8 additions & 4 deletions lib/internal/modules/package_json_reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const { toNamespacedPath } = require('path');

const cache = new SafeMap();

let manifest;

/**
*
* @param {string} jsonPath
Expand All @@ -22,10 +24,12 @@ function read(jsonPath) {
const result = { string, containsKeys };
const { getOptionValue } = require('internal/options');
if (string !== undefined) {
const manifest = getOptionValue('--experimental-policy') ?
require('internal/process/policy').manifest :
null;
if (manifest) {
if (manifest === undefined) {
manifest = getOptionValue('--experimental-policy') ?
require('internal/process/policy').manifest :
null;
}
if (manifest !== null) {
const jsonURL = pathToFileURL(jsonPath);
manifest.assertIntegrity(jsonURL, string);
}
Expand Down
Loading