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