• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Policies
2
3<!--introduced_in=v11.8.0-->
4<!-- type=misc -->
5
6> Stability: 1 - Experimental
7
8<!-- name=policy -->
9
10Node.js contains experimental support for creating policies on loading code.
11
12Policies are a security feature intended to allow guarantees
13about what code Node.js is able to load. The use of policies assumes
14safe practices for the policy files such as ensuring that policy
15files cannot be overwritten by the Node.js application by using
16file permissions.
17
18A best practice would be to ensure that the policy manifest is read-only for
19the running Node.js application and that the file cannot be changed
20by the running Node.js application in any way. A typical setup would be to
21create the policy file as a different user id than the one running Node.js
22and granting read permissions to the user id running Node.js.
23
24## Enabling
25
26<!-- type=misc -->
27
28The `--experimental-policy` flag can be used to enable features for policies
29when loading modules.
30
31Once this has been set, all modules must conform to a policy manifest file
32passed to the flag:
33
34```bash
35node --experimental-policy=policy.json app.js
36```
37
38The policy manifest will be used to enforce constraints on code loaded by
39Node.js.
40
41To mitigate tampering with policy files on disk, an integrity for
42the policy file itself may be provided via `--policy-integrity`.
43This allows running `node` and asserting the policy file contents
44even if the file is changed on disk.
45
46```bash
47node --experimental-policy=policy.json --policy-integrity="sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0" app.js
48```
49
50## Features
51
52### Error behavior
53
54When a policy check fails, Node.js by default will throw an error.
55It is possible to change the error behavior to one of a few possibilities
56by defining an "onerror" field in a policy manifest. The following values are
57available to change the behavior:
58
59* `"exit"`: will exit the process immediately.
60  No cleanup code will be allowed to run.
61* `"log"`: will log the error at the site of the failure.
62* `"throw"`: will throw a JS error at the site of the failure. This is the
63  default.
64
65```json
66{
67  "onerror": "log",
68  "resources": {
69    "./app/checked.js": {
70      "integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
71    }
72  }
73}
74```
75
76### Integrity checks
77
78Policy files must use integrity checks with Subresource Integrity strings
79compatible with the browser
80[integrity attribute](https://www.w3.org/TR/SRI/#the-integrity-attribute)
81associated with absolute URLs.
82
83When using `require()` or `import` all resources involved in loading are checked
84for integrity if a policy manifest has been specified. If a resource does not
85match the integrity listed in the manifest, an error will be thrown.
86
87An example policy file that would allow loading a file `checked.js`:
88
89```json
90{
91  "resources": {
92    "./app/checked.js": {
93      "integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
94    }
95  }
96}
97```
98
99Each resource listed in the policy manifest can be of one the following
100formats to determine its location:
101
1021. A [relative-URL string][] to a resource from the manifest such as `./resource.js`, `../resource.js`, or `/resource.js`.
1032. A complete URL string to a resource such as `file:///resource.js`.
104
105When loading resources the entire URL must match including search parameters
106and hash fragment. `./a.js?b` will not be used when attempting to load
107`./a.js` and vice versa.
108
109To generate integrity strings, a script such as
110`node -e 'process.stdout.write("sha256-");process.stdin.pipe(crypto.createHash("sha256").setEncoding("base64")).pipe(process.stdout)' < FILE`
111can be used.
112
113Integrity can be specified as the boolean value `true` to accept any
114body for the resource which can be useful for local development. It is not
115recommended in production since it would allow unexpected alteration of
116resources to be considered valid.
117
118### Dependency redirection
119
120An application may need to ship patched versions of modules or to prevent
121modules from allowing all modules access to all other modules. Redirection
122can be used by intercepting attempts to load the modules wishing to be
123replaced.
124
125```json
126{
127  "resources": {
128    "./app/checked.js": {
129      "dependencies": {
130        "fs": true,
131        "os": "./app/node_modules/alt-os",
132        "http": { "import": true }
133      }
134    }
135  }
136}
137```
138
139The dependencies are keyed by the requested specifier string and have values
140of either `true`, `null`, a string pointing to a module to be resolved,
141or a conditions object.
142
143The specifier string does not perform any searching and must match exactly what
144is provided to the `require()` or `import` except for a canonicalization step.
145Therefore, multiple specifiers may be needed in the policy if it uses multiple
146different strings to point to the same module (such as excluding the extension).
147
148Specifier strings are canonicalized but not resolved prior to be used for
149matching in order to have some compatibility with import maps, for example if a
150resource `file:///C:/app/server.js` was given the following redirection from a
151policy 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
165Any specifier used to load `file:///C:/app/utils.js` would then be intercepted
166and redirected to `file:///C:/app/utils-v2.js` instead regardless of using an
167absolute or relative specifier. However, if a specifier that is not an absolute
168or relative URL string is used, it would not be intercepted. So, if an import
169such as `import('#utils')` was used, it would not be intercepted.
170
171If the value of the redirection is `true`, a "dependencies" field at the top of
172the 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.
174
175If the value of the redirection is a string, it is resolved relative to
176the manifest and then immediately used without searching.
177
178Any specifier string for which resolution is attempted and that is not listed in
179the dependencies results in an error according to the policy.
180
181Redirection does not prevent access to APIs through means such as direct access
182to `require.cache` or through `module.constructor` which allow access to
183loading modules. Policy redirection only affects specifiers to `require()` and
184`import`. Other means, such as to prevent undesired access to APIs through
185variables, are necessary to lock down that path of loading modules.
186
187A boolean value of `true` for the dependencies map can be specified to allow a
188module to load any specifier without redirection. This can be useful for local
189development and may have some valid usage in production, but should be used
190only with care after auditing a module to ensure its behavior is valid.
191
192Similar to `"exports"` in `package.json`, dependencies can also be specified to
193be objects containing conditions which branch how dependencies are loaded. In
194the preceding example, `"http"` is allowed when the `"import"` condition is
195part of loading it.
196
197A value of `null` for the resolved value causes the resolution to fail. This
198can be used to ensure some kinds of dynamic access are explicitly prevented.
199
200Unknown values for the resolved module location cause failures but are
201not guaranteed to be forward compatible.
202
203#### Example: Patched dependency
204
205Redirected dependencies can provide attenuated or modified functionality as fits
206the application. For example, log data about timing of function durations by
207wrapping the original:
208
209```js
210const original = require('fn');
211module.exports = function fn(...args) {
212  console.time();
213  try {
214    return new.target ?
215      Reflect.construct(original, args) :
216      Reflect.apply(original, this, args);
217  } finally {
218    console.timeEnd();
219  }
220};
221```
222
223### Scopes
224
225Use the `"scopes"` field of a manifest to set configuration for many resources
226at once. The `"scopes"` field works by matching resources by their segments.
227If a scope or resource includes `"cascade": true`, unknown specifiers will
228be searched for in their containing scope. The containing scope for cascading
229is found by recursively reducing the resource URL by removing segments for
230[special schemes][], keeping trailing `"/"` suffixes, and removing the query and
231hash fragment. This leads to the eventual reduction of the URL to its origin.
232If the URL is non-special the scope will be located by the URL's origin. If no
233scope is found for the origin or in the case of opaque origins, a protocol
234string can be used as a scope. If no scope is found for the URL's protocol, a
235final empty string `""` scope will be used.
236
237Note, `blob:` URLs adopt their origin from the path they contain, and so a scope
238of `"blob:https://nodejs.org"` will have no effect since no URL can have an
239origin of `blob:https://nodejs.org`; URLs starting with
240`blob:https://nodejs.org/` will use `https://nodejs.org` for its origin and
241thus `https:` for its protocol scope. For opaque origin `blob:` URLs they will
242have `blob:` for their protocol scope since they do not adopt origins.
243
244#### Example
245
246```json
247{
248  "scopes": {
249    "file:///C:/app/": {},
250    "file:": {},
251    "": {}
252  }
253}
254```
255
256Given a file located at `file:///C:/app/bin/main.js`, the following scopes would
257be checked in order:
258
2591. `"file:///C:/app/bin/"`
260
261This 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
263would be skipped. Adding this scope to the policy would cause it to be used
264prior to the `"file:///C:/app/"` scope.
265
2662. `"file:///C:/app/"`
267
268This 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
270determine the policy for the resource at `file:///C:/app/bin/main.js`. If the
271scope has `"cascade": true`, any unsatisfied queries about the resource would
272delegate to the next relevant scope for `file:///C:/app/bin/main.js`, `"file:"`.
273
2743. `"file:///C:/"`
275
276This determines the policy for all file based resources within `"file:///C:/"`.
277This is not in the `"scopes"` field of the policy and would be skipped. It would
278not be used for `file:///C:/app/bin/main.js` unless `"file:///"` is set to
279cascade or is not in the `"scopes"` of the policy.
280
2814. `"file:///"`
282
283This determines the policy for all file based resources on the `localhost`. This
284is not in the `"scopes"` field of the policy and would be skipped. It would not
285be used for `file:///C:/app/bin/main.js` unless `"file:///"` is set to cascade
286or is not in the `"scopes"` of the policy.
287
2885. `"file:"`
289
290This determines the policy for all file based resources. It would not be used
291for `file:///C:/app/bin/main.js` unless `"file:///"` is set to cascade or is not
292in the `"scopes"` of the policy.
293
2946. `""`
295
296This 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
299#### Integrity using scopes
300
301Setting an integrity to `true` on a scope will set the integrity for any
302resource not found in the manifest to `true`.
303
304Setting an integrity to `null` on a scope will set the integrity for any
305resource not found in the manifest to fail matching.
306
307Not including an integrity is the same as setting the integrity to `null`.
308
309`"cascade"` for integrity checks will be ignored if `"integrity"` is explicitly
310set.
311
312The following example allows loading any file:
313
314```json
315{
316  "scopes": {
317    "file:": {
318      "integrity": true
319    }
320  }
321}
322```
323
324#### Dependency redirection using scopes
325
326The following example, would allow access to `fs` for all resources within
327`./app/`:
328
329```json
330{
331  "resources": {
332    "./app/checked.js": {
333      "cascade": true,
334      "integrity": true
335    }
336  },
337  "scopes": {
338    "./app/": {
339      "dependencies": {
340        "fs": true
341      }
342    }
343  }
344}
345```
346
347The following example, would allow access to `fs` for all `data:` resources:
348
349```json
350{
351  "resources": {
352    "data:text/javascript,import('fs');": {
353      "cascade": true,
354      "integrity": true
355    }
356  },
357  "scopes": {
358    "data:": {
359      "dependencies": {
360        "fs": true
361      }
362    }
363  }
364}
365```
366
367#### Example: [import maps][] emulation
368
369Given 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
404Import 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`.
406Policies require this to be opt-in since it enables all resources of the
407application cross linkage which doesn't make sense for many scenarios. They also
408assume any given scope has access to any scope above its allowed dependencies;
409all scopes emulating import maps must set `"cascade": true`.
410
411Import maps only have a single top level scope for their "imports". So for
412emulating `"imports"` use the `""` scope. For emulating `"scopes"` use the
413`"scopes"` in a similar manner to how `"scopes"` works in import maps.
414
415Caveats: Policies do not use string matching for various finding of scope. They
416do URL traversals. This means things like `blob:` and `data:` URLs might not be
417entirely interoperable between the two systems. For example import maps can
418partially match a `data:` or `blob:` URL by partitioning the URL on a `/`
419character, policies intentionally cannot. For `blob:` URLs import map scopes do
420not adopt the origin of the `blob:` URL.
421
422Additionally, 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
427[special schemes]: https://url.spec.whatwg.org/#special-scheme
428