Skip to content

Commit 18b04ab

Browse files
bmeckrichardlau
authored andcommitted
policy: implement scopes field
PR-URL: #34552 Reviewed-By: James M Snell <[email protected]>
1 parent f6a5999 commit 18b04ab

19 files changed

+1154
-339
lines changed

doc/api/policy.md

+89
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,93 @@ module.exports = function fn(...args) {
196196
};
197197
```
198198

199+
### Scopes
200+
201+
Use the `"scopes"` field of a manifest to set configuration for many resources
202+
at once. The `"scopes"` field works by matching resources by their segments.
203+
If a scope or resource includes `"cascade": true` unknown specifiers will
204+
be searched for in their containing scope. The containing scope for cascading
205+
is found by recursively reducing the resource URL by removing segments for
206+
[special schemes][], keeping trailing `"/"` suffixes and removing the query and
207+
hash fragment. This leads to the eventual reduction of the URL to its origin.
208+
If the URL is non-special the scope will be located by the URL's origin. If no
209+
scope is found for the origin or in the case of opaque origins, a protocol
210+
string can be used as a scope.
211+
212+
Note, `blob:` URLs adopt their origin from the path they contain, and so a scope
213+
of `"blob:https://nodejs.org"` will have no effect since no URL can have an
214+
origin of `blob:https://nodejs.org`; URLs starting with
215+
`blob:https://nodejs.org/` will use `https://nodejs.org` for its origin and
216+
thus `https:` for its protocol scope. For opaque origin `blob:` URLs they will
217+
have `blob:` for their protocol scope since they do not adopt origins.
218+
219+
#### Integrity Using Scopes
220+
221+
Setting an integrity to `true` on a scope will set the integrity for any
222+
resource not found in the manifest to `true`.
223+
224+
Setting an integrity to `null` on a scope will set the integrity for any
225+
resource not found in the manifest to fail matching.
226+
227+
Not including an integrity is the same as setting the integrity to `null`.
228+
229+
`"cascade"` for integrity checks will be ignored if `"integrity"` is explicitly
230+
set.
231+
232+
The following example allows loading any file:
233+
234+
```json
235+
{
236+
"scopes": {
237+
"file:": {
238+
"integrity": true
239+
}
240+
}
241+
}
242+
```
243+
244+
#### Dependency Redirection Using Scopes
245+
246+
The following example, would allow access to `fs` for all resources within
247+
`./app/`:
248+
249+
```json
250+
{
251+
"resources": {
252+
"./app/checked.js": {
253+
"cascade": true,
254+
"integrity": true
255+
}
256+
},
257+
"scopes": {
258+
"./app/": {
259+
"dependencies": {
260+
"fs": true
261+
}
262+
}
263+
}
264+
}
265+
```
266+
267+
The following example, would allow access to `fs` for all `data:` resources:
268+
269+
```json
270+
{
271+
"resources": {
272+
"data:text/javascript,import('fs');": {
273+
"cascade": true,
274+
"integrity": true
275+
}
276+
},
277+
"scopes": {
278+
"data:": {
279+
"dependencies": {
280+
"fs": true
281+
}
282+
}
283+
}
284+
}
285+
```
286+
199287
[relative url string]: https://url.spec.whatwg.org/#relative-url-with-fragment-string
288+
[special schemes]: https://url.spec.whatwg.org/#special-scheme

lib/internal/modules/cjs/loader.js

+11-10
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ const {
4646
SafeMap,
4747
String,
4848
StringPrototypeEndsWith,
49-
StringPrototypeIndexOf,
5049
StringPrototypeLastIndexOf,
50+
StringPrototypeIndexOf,
5151
StringPrototypeMatch,
5252
StringPrototypeSlice,
5353
StringPrototypeStartsWith,
@@ -81,8 +81,9 @@ const { getOptionValue } = require('internal/options');
8181
const enableSourceMaps = getOptionValue('--enable-source-maps');
8282
const preserveSymlinks = getOptionValue('--preserve-symlinks');
8383
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
84-
const manifest = getOptionValue('--experimental-policy') ?
85-
require('internal/process/policy').manifest :
84+
// Do not eagerly grab .manifest, it may be in TDZ
85+
const policy = getOptionValue('--experimental-policy') ?
86+
require('internal/process/policy') :
8687
null;
8788
const { compileFunction } = internalBinding('contextify');
8889

@@ -1029,10 +1030,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
10291030
Module.prototype._compile = function(content, filename) {
10301031
let moduleURL;
10311032
let redirects;
1032-
if (manifest) {
1033+
if (policy?.manifest) {
10331034
moduleURL = pathToFileURL(filename);
1034-
redirects = manifest.getRedirector(moduleURL);
1035-
manifest.assertIntegrity(moduleURL, content);
1035+
redirects = policy.manifest.getDependencyMapper(moduleURL);
1036+
policy.manifest.assertIntegrity(moduleURL, content);
10361037
}
10371038

10381039
maybeCacheSourceMap(filename, content, this);
@@ -1101,9 +1102,9 @@ Module._extensions['.js'] = function(module, filename) {
11011102
Module._extensions['.json'] = function(module, filename) {
11021103
const content = fs.readFileSync(filename, 'utf8');
11031104

1104-
if (manifest) {
1105+
if (policy?.manifest) {
11051106
const moduleURL = pathToFileURL(filename);
1106-
manifest.assertIntegrity(moduleURL, content);
1107+
policy.manifest.assertIntegrity(moduleURL, content);
11071108
}
11081109

11091110
try {
@@ -1117,10 +1118,10 @@ Module._extensions['.json'] = function(module, filename) {
11171118

11181119
// Native extension for .node
11191120
Module._extensions['.node'] = function(module, filename) {
1120-
if (manifest) {
1121+
if (policy?.manifest) {
11211122
const content = fs.readFileSync(filename);
11221123
const moduleURL = pathToFileURL(filename);
1123-
manifest.assertIntegrity(moduleURL, content);
1124+
policy.manifest.assertIntegrity(moduleURL, content);
11241125
}
11251126
// Be aware this doesn't use `content`
11261127
return process.dlopen(module, path.toNamespacedPath(filename));

lib/internal/modules/esm/get_source.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use strict';
22

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

89
const { Buffer } = require('buffer');
@@ -33,8 +34,8 @@ async function defaultGetSource(url, { format } = {}, defaultGetSource) {
3334
} else {
3435
throw new ERR_INVALID_URL_SCHEME(['file', 'data']);
3536
}
36-
if (manifest) {
37-
manifest.assertIntegrity(parsed, source);
37+
if (policy?.manifest) {
38+
policy.manifest.assertIntegrity(parsed, source);
3839
}
3940
return { source };
4041
}

lib/internal/modules/esm/resolve.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ const {
3030
Stats,
3131
} = require('fs');
3232
const { getOptionValue } = require('internal/options');
33-
const manifest = getOptionValue('--experimental-policy') ?
34-
require('internal/process/policy').manifest :
33+
// Do not eagerly grab .manifest, it may be in TDZ
34+
const policy = getOptionValue('--experimental-policy') ?
35+
require('internal/process/policy') :
3536
null;
3637
const { sep, relative } = require('path');
3738
const preserveSymlinks = getOptionValue('--preserve-symlinks');
@@ -714,8 +715,8 @@ function resolveAsCommonJS(specifier, parentURL) {
714715

715716
function defaultResolve(specifier, context = {}, defaultResolveUnused) {
716717
let { parentURL, conditions } = context;
717-
if (manifest) {
718-
const redirects = manifest.getRedirector(parentURL);
718+
if (parentURL && policy?.manifest) {
719+
const redirects = policy.manifest.getDependencyMapper(parentURL);
719720
if (redirects) {
720721
const { resolve, reaction } = redirects;
721722
const destination = resolve(specifier, new SafeSet(conditions));

lib/internal/modules/package_json_reader.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const { toNamespacedPath } = require('path');
77

88
const cache = new SafeMap();
99

10+
let manifest;
11+
1012
/**
1113
*
1214
* @param {string} jsonPath
@@ -22,10 +24,12 @@ function read(jsonPath) {
2224
const result = { string, containsKeys };
2325
const { getOptionValue } = require('internal/options');
2426
if (string !== undefined) {
25-
const manifest = getOptionValue('--experimental-policy') ?
26-
require('internal/process/policy').manifest :
27-
null;
28-
if (manifest) {
27+
if (manifest === undefined) {
28+
manifest = getOptionValue('--experimental-policy') ?
29+
require('internal/process/policy').manifest :
30+
null;
31+
}
32+
if (manifest !== null) {
2933
const jsonURL = pathToFileURL(jsonPath);
3034
manifest.assertIntegrity(jsonURL, string);
3135
}

0 commit comments

Comments
 (0)