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