Skip to content

Commit 8fa3850

Browse files
bmecktargos
authored andcommitted
policy: canonicalize before resolving specifiers
PR-URL: #37863 Co-authored-by: Antoine du Hamel <[email protected]> Co-authored-by: James M Snell <[email protected]> Reviewed-By: Guy Bedford <[email protected]>
1 parent a7a217b commit 8fa3850

12 files changed

+772
-351
lines changed

doc/api/errors.md

+7
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,13 @@ A policy manifest resource had an invalid value for one of its fields. Update
18341834
the manifest entry to match in order to resolve this error. See the
18351835
documentation for [policy][] manifests for more information.
18361836

1837+
<a id="ERR_MANIFEST_INVALID_SPECIFIER"></a>
1838+
### `ERR_MANIFEST_INVALID_SPECIFIER`
1839+
1840+
A policy manifest resource had an invalid value for one of its dependency
1841+
mappings. Update the manifest entry to match to resolve this error. See the
1842+
documentation for [policy][] manifests for more information.
1843+
18371844
<a id="ERR_MANIFEST_PARSE_POLICY"></a>
18381845
### `ERR_MANIFEST_PARSE_POLICY`
18391846

doc/api/policy.md

+152-13
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ by defining an "onerror" field in a policy manifest. The following values are
5757
available to change the behavior:
5858

5959
* `"exit"`: will exit the process immediately.
60-
No cleanup code will be allowed to run.
60+
No cleanup code will be allowed to run.
6161
* `"log"`: will log the error at the site of the failure.
6262
* `"throw"`: will throw a JS error at the site of the failure. This is the
6363
default.
@@ -80,9 +80,9 @@ compatible with the browser
8080
[integrity attribute](https://www.w3.org/TR/SRI/#the-integrity-attribute)
8181
associated with absolute URLs.
8282

83-
When using `require()` all resources involved in loading are checked for
84-
integrity if a policy manifest has been specified. If a resource does not match
85-
the integrity listed in the manifest, an error will be thrown.
83+
When using `require()` or `import` all resources involved in loading are checked
84+
for integrity if a policy manifest has been specified. If a resource does not
85+
match the integrity listed in the manifest, an error will be thrown.
8686

8787
An example policy file that would allow loading a file `checked.js`:
8888

@@ -107,7 +107,7 @@ and hash fragment. `./a.js?b` will not be used when attempting to load
107107
`./a.js` and vice versa.
108108

109109
To generate integrity strings, a script such as
110-
`printf "sha384-$(cat checked.js | openssl dgst -sha384 -binary | base64)"`
110+
`node -e 'process.stdout.write("sha256-");process.stdin.pipe(crypto.createHash("sha256").setEncoding("base64")).pipe(process.stdout)' < FILE`
111111
can be used.
112112

113113
Integrity can be specified as the boolean value `true` to accept any
@@ -140,13 +140,37 @@ The dependencies are keyed by the requested specifier string and have values
140140
of either `true`, `null`, a string pointing to a module to be resolved,
141141
or a conditions object.
142142

143-
The specifier string does not perform any searching and must match exactly
144-
what is provided to the `require()` or `import`. Therefore, multiple specifiers
145-
may be needed in the policy if it uses multiple different strings to point
146-
to the same module (such as excluding the extension).
143+
The specifier string does not perform any searching and must match exactly what
144+
is provided to the `require()` or `import` except for a canonicalization step.
145+
Therefore, multiple specifiers may be needed in the policy if it uses multiple
146+
different strings to point to the same module (such as excluding the extension).
147147

148-
If the value of the redirection is `true` the default searching algorithms are
149-
used to find the module.
148+
Specifier strings are canonicalized but not resolved prior to be used for
149+
matching in order to have some compatibility with import maps, for example if a
150+
resource `file:///C:/app/server.js` was given the following redirection from a
151+
policy located at `file:///C:/app/policy.json`:
152+
153+
```json
154+
{
155+
"resources": {
156+
"file:///C:/app/utils.js": {
157+
"dependencies": {
158+
"./utils.js": "./utils-v2.js"
159+
}
160+
}
161+
}
162+
}
163+
```
164+
165+
Any specifier used to load `file:///C:/app/utils.js` would then be intercepted
166+
and redirected to `file:///C:/app/utils-v2.js` instead regardless of using an
167+
absolute or relative specifier. However, if a specifier that is not an absolute
168+
or relative URL string is used, it would not be intercepted. So, if an import
169+
such as `import('#utils')` was used, it would not be intercepted.
170+
171+
If the value of the redirection is `true`, a "dependencies" field at the top of
172+
the policy file will be used. If that field at the top of the policy file is
173+
`true` the default node searching algorithms are used to find the module.
150174

151175
If the value of the redirection is a string, it is resolved relative to
152176
the manifest and then immediately used without searching.
@@ -207,7 +231,8 @@ is found by recursively reducing the resource URL by removing segments for
207231
hash fragment. This leads to the eventual reduction of the URL to its origin.
208232
If the URL is non-special the scope will be located by the URL's origin. If no
209233
scope is found for the origin or in the case of opaque origins, a protocol
210-
string can be used as a scope.
234+
string can be used as a scope. If no scope is found for the URL's protocol, a
235+
final empty string `""` scope will be used.
211236

212237
Note, `blob:` URLs adopt their origin from the path they contain, and so a scope
213238
of `"blob:https://nodejs.org"` will have no effect since no URL can have an
@@ -216,6 +241,61 @@ origin of `blob:https://nodejs.org`; URLs starting with
216241
thus `https:` for its protocol scope. For opaque origin `blob:` URLs they will
217242
have `blob:` for their protocol scope since they do not adopt origins.
218243

244+
#### Example
245+
246+
```json
247+
{
248+
"scopes": {
249+
"file:///C:/app/": {},
250+
"file:": {},
251+
"": {}
252+
}
253+
}
254+
```
255+
256+
Given a file located at `file:///C:/app/bin/main.js`, the following scopes would
257+
be checked in order:
258+
259+
1. `"file:///C:/app/bin/"`
260+
261+
This determines the policy for all file based resources within
262+
`"file:///C:/app/bin/"`. This is not in the `"scopes"` field of the policy and
263+
would be skipped. Adding this scope to the policy would cause it to be used
264+
prior to the `"file:///C:/app/"` scope.
265+
266+
2. `"file:///C:/app/"`
267+
268+
This determines the policy for all file based resources within
269+
`"file:///C:/app/"`. This is in the `"scopes"` field of the policy and it would
270+
determine the policy for the resource at `file:///C:/app/bin/main.js`. If the
271+
scope has `"cascade": true`, any unsatisfied queries about the resource would
272+
delegate to the next relevant scope for `file:///C:/app/bin/main.js`, `"file:"`.
273+
274+
3. `"file:///C:/"`
275+
276+
This determines the policy for all file based resources within `"file:///C:/"`.
277+
This is not in the `"scopes"` field of the policy and would be skipped. It would
278+
not be used for `file:///C:/app/bin/main.js` unless `"file:///"` is set to
279+
cascade or is not in the `"scopes"` of the policy.
280+
281+
4. `"file:///"`
282+
283+
This determines the policy for all file based resources on the `localhost`. This
284+
is not in the `"scopes"` field of the policy and would be skipped. It would not
285+
be used for `file:///C:/app/bin/main.js` unless `"file:///"` is set to cascade
286+
or is not in the `"scopes"` of the policy.
287+
288+
5. `"file:"`
289+
290+
This determines the policy for all file based resources. It would not be used
291+
for `file:///C:/app/bin/main.js` unless `"file:///"` is set to cascade or is not
292+
in the `"scopes"` of the policy.
293+
294+
6. `""`
295+
296+
This determines the policy for all resources. It would not be used for
297+
`file:///C:/app/bin/main.js` unless `"file:"` is set to cascade.
298+
219299
#### Integrity using scopes
220300

221301
Setting an integrity to `true` on a scope will set the integrity for any
@@ -284,5 +364,64 @@ The following example, would allow access to `fs` for all `data:` resources:
284364
}
285365
```
286366

287-
[relative-URL string]: https://url.spec.whatwg.org/#relative-url-with-fragment-string
367+
#### Example: [import maps][] emulation
368+
369+
Given an import map:
370+
371+
```json
372+
{
373+
"imports": {
374+
"react": "./app/node_modules/react/index.js"
375+
},
376+
"scopes": {
377+
"./ssr/": {
378+
"react": "./app/node_modules/server-side-react/index.js"
379+
}
380+
}
381+
}
382+
```
383+
384+
```json
385+
{
386+
"dependencies": true,
387+
"scopes": {
388+
"": {
389+
"cascade": true,
390+
"dependencies": {
391+
"react": "./app/node_modules/react/index.js"
392+
}
393+
},
394+
"./ssr/": {
395+
"cascade": true,
396+
"dependencies": {
397+
"react": "./app/node_modules/server-side-react/index.js"
398+
}
399+
}
400+
}
401+
}
402+
```
403+
404+
Import maps assume you can get any resource by default. This means
405+
`"dependencies"` at the top level of the policy should be set to `true`.
406+
Policies require this to be opt-in since it enables all resources of the
407+
application cross linkage which doesn't make sense for many scenarios. They also
408+
assume any given scope has access to any scope above its allowed dependencies;
409+
all scopes emulating import maps must set `"cascade": true`.
410+
411+
Import maps only have a single top level scope for their "imports". So for
412+
emulating `"imports"` use the `""` scope. For emulating `"scopes"` use the
413+
`"scopes"` in a similar manner to how `"scopes"` works in import maps.
414+
415+
Caveats: Policies do not use string matching for various finding of scope. They
416+
do URL traversals. This means things like `blob:` and `data:` URLs might not be
417+
entirely interoperable between the two systems. For example import maps can
418+
partially match a `data:` or `blob:` URL by partitioning the URL on a `/`
419+
character, policies intentionally cannot. For `blob:` URLs import map scopes do
420+
not adopt the origin of the `blob:` URL.
421+
422+
Additionally, import maps only work on `import` so it may be desirable to add a
423+
`"import"` condition to all dependency mappings.
424+
425+
[import maps]: https://url.spec.whatwg.org/#relative-url-with-fragment-string
426+
[relative-url string]: https://url.spec.whatwg.org/#relative-url-with-fragment-string
288427
[special schemes]: https://url.spec.whatwg.org/#special-scheme

lib/internal/errors.js

+3
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,9 @@ E('ERR_MANIFEST_INTEGRITY_MISMATCH',
13601360
E('ERR_MANIFEST_INVALID_RESOURCE_FIELD',
13611361
'Manifest resource %s has invalid property value for %s',
13621362
TypeError);
1363+
E('ERR_MANIFEST_INVALID_SPECIFIER',
1364+
'Manifest resource %s has invalid dependency mapping %s',
1365+
TypeError);
13631366
E('ERR_MANIFEST_TDZ', 'Manifest initialization has not yet run', Error);
13641367
E('ERR_MANIFEST_UNKNOWN_ONERROR',
13651368
'Manifest specified unknown error behavior "%s".',

0 commit comments

Comments
 (0)