The Permission Model
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Node.js 26 (26.10.0, released 2026-09-21, the Current line that becomes LTS on 2026-10-28), with the differences
of Node.js 24 "Krypton" (24.21.0, Active LTS) and Node.js 22 "Jod" (22.23.3, Maintenance) called out; versions and stability levels were
checked against the official documentation on 2026-10-04, and every example below was run on all three lines unless the text says
otherwise. The permission model lets you start a Node.js process with most of its authority switched off, and then hand back only the
capabilities the program needs: reading one folder, writing one folder, nothing else. The model as a whole (--permission, the
--allow-fs-read and --allow-fs-write flags, process.permission) is Stability 2 (Stable) since v23.5.0 and v22.13.0. The other
--allow-* flags are still Stability 1.1 (Active development), --allow-inspector is Stability 1.0 (Early development), and the
configuration file that can carry the same settings is Stability 1.2 (Release candidate) on Node.js 26 but 1.0 (Early development) on
Node.js 24 and 22. The model is deliberately limited: the official documentation describes it as a "seat belt" for trusted code, not a
sandbox, and the second half of this page is about exactly where that line is. The running scenario is the Bookshelf application
described in Introduction & Architecture; the broader security checklist (threat
model, supply chain, injection) is in Security Best Practices.
The stability and version map
| API | Stability (Node.js 26 docs) | Version notes |
|---|---|---|
Permission model, |
2 Stable |
Added in v20.0.0 as |
|
Stable (part of the model) |
Available whenever the model is on, on all three lines. |
|
Not labelled separately in the docs |
Observed: present on Node.js 26.10.0 and 24.21.0, |
|
1.1 Active development |
Added v20.0.0, v20.0.0, v21.6.0 and v22.3.0 respectively (see the timeline below for the 20-line backports). |
|
1.0 Early development |
Added in v25.0.0 and v24.12.0. |
|
1.1 Active development |
Added in v25.0.0 only. Not available on Node.js 24 or 22 ( |
|
1.1 Active development |
Added in v26.1.0. Needs a build that includes |
|
1.1 Active development |
Added in v26.7.0, and v24.21.0 on the 24 line. |
|
1.1 Active development |
Added in v26.9.0, only meaningful together with the experimental virtual file system. |
|
Not labelled separately in the docs |
Added in v25.8.0 and v24.20.0. Not available on Node.js 22 ( |
Configuration file |
1.2 Release candidate on 26 (since v26.7.0) |
1.0 Early development on 24 and 22. The file mechanism was added in v23.10.0 and v22.16.0. |
What it is, and what it is not
Normally a Node.js process can do anything its operating-system user can do: read any file, open any socket, spawn any program. The
permission model flips that default for the Node.js APIs: once --permission is on, each class of operation is refused with an
ERR_ACCESS_DENIED error unless a matching --allow-* flag was given at start-up. The point is to stop trusted code (yours, your
dependencies on a good day) from accidentally touching things it was never meant to touch: a build script that writes outside its
output folder, a report generator that opens a network socket, a dependency that starts a shell. That is the "seat belt" the official
documentation talks about.
It is not a sandbox. The documentation is explicit that the feature does not protect against malicious code, because Node.js trusts any code it is asked to run, and that malicious code can bypass the model and execute arbitrary code outside its restrictions. Treat it as one layer of defence in depth next to operating-system isolation (separate users, containers, seccomp or AppArmor profiles) and supply-chain hygiene, not as a replacement for them. The limits are listed with working demonstrations in Where the seat belt ends.
| Permission model (seat belt) | Sandbox for untrusted code | |
|---|---|---|
Threat it addresses |
Honest mistakes and over-broad access by code you chose to run |
Code written by an attacker |
Enforced by |
Checks inside the Node.js runtime, on Node.js APIs |
The operating system or a hypervisor, outside the process |
Trusted computing base |
The runtime and every API that forgot a check (see the limits below) |
A small, hardened boundary |
Typical use |
Scripts, CLIs and build steps that need a tiny, known set of capabilities; auditing what a program really touches |
Running plug-ins, user-submitted code or third-party jobs |
Source: Node.js API: Permissions
Turning it on: deny by default
node --permission app.js is all it takes. Every restricted capability is off until granted. The first example probes four things
(read a file, write a file, spawn a child process, start a worker thread) with and without grants. It assumes a folder app/ that
contains probe.mjs, a data/books.json file and an empty out/ folder.
import { readFileSync, writeFileSync } from 'node:fs';
import { spawn } from 'node:child_process';
import { Worker } from 'node:worker_threads';
function attempt(label, fn) {
try {
fn();
console.log(`${label}: allowed`);
} catch (err) {
console.log(`${label}: ${err.code} (${err.permission ?? 'n/a'})`);
}
}
attempt('read data/books.json', () => readFileSync(new URL('./data/books.json', import.meta.url)));
attempt('write out/report.txt', () => writeFileSync(new URL('./out/report.txt', import.meta.url), 'ok'));
attempt('spawn child', () => spawn(process.execPath, ['-e', '0']).on('error', () => {}));
attempt('start worker', () => new Worker('0', { eval: true }));
node --permission probe.mjs
node --permission --allow-fs-read="$PWD/data" --allow-fs-write="$PWD/out" probe.mjs
Source: Node.js API: --permission
The output below is identical on Node.js 26.10.0, 24.21.0 and 22.23.3 (observed). The entry point itself is always readable, which is why
the script can start without any --allow-fs-read; that implicit read of the entry point, and of --require modules, is documented
from v24.2.0 and v22.17.0.
read data/books.json: ERR_ACCESS_DENIED (FileSystemRead)
write out/report.txt: ERR_ACCESS_DENIED (FileSystemWrite)
spawn child: ERR_ACCESS_DENIED (ChildProcess)
start worker: ERR_ACCESS_DENIED (WorkerThreads)
read data/books.json: allowed
write out/report.txt: allowed
spawn child: ERR_ACCESS_DENIED (ChildProcess)
start worker: ERR_ACCESS_DENIED (WorkerThreads)
The error carries two useful properties: code (always ERR_ACCESS_DENIED) and permission, which names the denied class
(FileSystemRead, FileSystemWrite, ChildProcess, WorkerThreads, Net, Inspector, WASI, FFI); file system errors also set
resource to the path. Catch on err.code, log err.permission, and the denial is easy to diagnose. A module that fails this way at
start-up (for example an ES module that imports a file you did not grant) fails with the same error before any of your code runs.
The flags
| Flag | Scope for permission.has() |
err.permission |
What it gates |
|---|---|---|---|
|
|
|
Reading files and directories through |
|
|
|
Creating, writing, renaming and deleting through |
|
|
|
|
|
|
|
Creating |
|
|
none (code |
Loading native addons ( |
|
|
|
Creating |
|
|
|
Network access (observed on 26 only; see below). |
|
|
|
Connecting through the inspector protocol ( |
|
|
|
|
|
(not listed in the |
|
OpenSSL STORE loaders, for example loading a key from a URL passed to |
|
(not listed in the |
|
Mounting a virtual file system (experimental). |
| Flag | Node.js 26.10.0 | Node.js 24.21.0 | Node.js 22.23.3 |
|---|---|---|---|
|
yes |
yes |
yes |
|
yes |
yes |
|
|
yes |
yes |
|
|
yes |
|
|
|
yes |
yes |
|
A few behaviours of the flags themselves are worth knowing before you rely on them:
-
Passing
--permissiontogether with--allow-child-process,--allow-worker,--allow-addons,--allow-ffi,--allow-inspectoror--allow-openssl-storeprints aSecurityWarning("must be used with extreme caution. It could invalidate the permission model"). Observed codes on 26 and 24 arePERM0001(addons),PERM0003(FFI),PERM0004(inspector),PERM0006(worker) andPERM0007(OpenSSL STORE); Node.js 22 prints the same message without a code.--allow-netprints anExperimentalWarninginstead. -
The permission model is initialised after the Node.js environment is set up. Flags that read files early, such as
--env-fileand--openssl-config, run before it and are not subject to the rules, and neither are V8 flags changed later withv8.setFlagsFromString(). -
A worker thread does not inherit the model: the documentation says so under its constraints, which is one reason
--allow-workercarries a warning.
File system grants
--allow-fs-read and --allow-fs-write take a path, may be repeated, and accept three forms: (everything), an absolute path, or a
path relative to the current working directory. Comma-separated lists stopped working in v20.7.0; passing one prints a warning, so use one
flag per path. A path ending in a wildcard matches by prefix, and every character after the first is ignored (/home/.js behaves
like /home/). If the path names a directory that exists when the process starts, Node.js appends the wildcard for you, so
--allow-fs-read=./data grants the folder and everything below it; a folder that does not exist yet must be written with an explicit
/ (--allow-fs-write=./cache/) to be usable once it is created.
| Flag | Meaning |
|---|---|
|
Every read is allowed. Useful with |
|
The |
|
Two grants: a folder and a single file. |
|
A folder that may not exist yet; the explicit wildcard is required. |
|
Prefix match: |
Two sharp edges are documented. First, some node:fs operations act on an already-open file descriptor rather than a path (fsync,
fdatasync, fchmod, fchown and their synchronous variants, plus the equivalent FileHandle methods). They cannot be tied to a path
grant, so with the model on they are disabled and throw ERR_ACCESS_DENIED whatever the descriptor’s origin. Second, the constraints
section also warns that using existing file descriptors through node:fs bypasses the model. Both are consequences of the same fact:
the check happens where a path enters the API, and a descriptor has no path.
Child processes, workers, addons, WASI, network, inspector and FFI
Each remaining flag is an all-or-nothing switch for one subsystem. The examples below show what a denial looks like; every one of them was run.
import { Session } from 'node:inspector';
import { WASI } from 'node:wasi';
import { createRequire } from 'node:module';
function attempt(label, fn) {
try { fn(); console.log(`${label}: allowed`); }
catch (err) { console.log(`${label}: ${err.code} (${err.permission ?? 'n/a'})`); }
}
attempt('inspector connect', () => new Session().connect());
attempt('WASI instance', () => new WASI({ version: 'preview1' }));
attempt('load addon', () => createRequire(import.meta.url)('./fake.node'));
Source: Node.js API: --allow-inspector
With fake.node an empty file that is readable (--allow-fs-read="$PWD/fake.node"), ERR_DLOPEN_DISABLED is the observed result on all
three lines; adding --allow-addons changes it to ERR_DLOPEN_FAILED (the file is not a real addon), proving that the permission check
is passed. The inspector and WASI attempts report ERR_ACCESS_DENIED with Inspector and WASI on all three lines (observed; for
inspector on Node.js 22 this works even though the --allow-inspector flag does not exist there).
-
Child processes (
--allow-child-process). Since v24.4.0 and v22.18.0 a permittedchild_process.fork()passes the model’s flags to the child automatically, andspawn()passes them through theNODE_OPTIONSenvironment variable, so a Node.js child is as restricted as its parent. A child that is not Node.js is not restricted by anything: see Where the seat belt ends. More detail on the APIs is in Child Processes. -
Worker threads (
--allow-worker). Without it,new Worker()throws. With it, the documentation notes that the model does not inherit to the worker. See Worker Threads. -
Native addons (
--allow-addons). Without it,require()of a.nodefile throwsERR_DLOPEN_DISABLED; this is why a dependency with a native module breaks the first time you turn the model on. -
WASI (
--allow-wasi). Creating aWASIinstance is refused in the main process unless this flag is given. A WASI guest that gets preopened directories reaches the real file system, which is the reason the flag exists. -
Network (
--allow-net, Node.js 26 only). The check is applied when the socket is created or bound. Observed on 26.10.0: a clientnet.connect()does not throw; the socket emits an'error'event withcodeERR_ACCESS_DENIED(and nopermissionproperty), andserver.listen()throws from the bind. On Node.js 24 and 22 the same program connects normally, because there is no network scope:--permissiondoes not restrict sockets on those lines (observed). The documentation shows--allow-netonly as a plain switch, with no host or port argument. -
Inspector (
--allow-inspector). Stops the process connecting through the inspector protocol and, as the documentation puts it, theSIGUSR1listener is not created. It does not stop another process from being asked to open its inspector; see the limits. -
FFI (
--allow-ffi, Node.js 26).node:ffiis Experimental and only exists in builds with FFI support; observed on 26.10.0, creating aDynamicLibraryunder--permissionfails withERR_ACCESS_DENIEDandpermissionFFI.
import net from 'node:net';
const sock = net.connect(9, '127.0.0.1');
sock.on('error', (e) => console.log(e.code, '|', e.permission, '|', e.message));
Source: Node.js API: --allow-net
node --permission net.mjs on 26.10.0, and with --allow-net (nothing is listening on port 9)ERR_ACCESS_DENIED | undefined | connect ERR_ACCESS_DENIED Access to this API has been restricted. Use --allow-net to manage permissions.
ECONNREFUSED | undefined | connect ECONNREFUSED 127.0.0.1:9
process.permission: has() and drop()
When the model is enabled (by --permission or --permission-audit), the process object gains a permission property. Without the
flag it is undefined, so feature-detect with process.permission?.has(…).
permission.has(scope[, reference]) answers "may I do this?" without trying it. The scopes are fs.read, fs.write, child,
worker, net, inspector, wasi, addon and ffi. With a reference (a path) it asks about one resource. Observed: without one, fs.read and
fs.write print false when only a folder was granted, so the scope-only form behaves as "is everything allowed?".
const scopes = ['fs.read', 'fs.write', 'child', 'worker', 'net', 'inspector', 'wasi', 'addon', 'ffi'];
for (const scope of scopes) {
console.log(scope.padEnd(10), process.permission.has(scope));
}
console.log('fs.read data :', process.permission.has('fs.read', new URL('./data/books.json', import.meta.url).pathname));
console.log('fs.write out :', process.permission.has('fs.write', new URL('./out/', import.meta.url).pathname));
console.log('drop typeof :', typeof process.permission.drop);
node --permission --allow-fs-read="$PWD/data" --allow-fs-write="$PWD/out" --allow-worker has.mjs (Node.js 26.10.0)fs.read false
fs.write false
child false
worker true
net false
inspector false
wasi false
addon false
ffi false
fs.read data : true
fs.write out : true
drop typeof : function
Node.js 24.21.0 prints the same lines. On 22.23.3 the output is identical except drop typeof : undefined, and (observed) asking about
net, ffi or an invented scope returns false instead of throwing, so a false there does not prove the scope exists.
permission.drop(scope[, reference]) revokes a permission at run time and is irreversible. The pattern it enables is "read your
configuration at start-up, then close the door": load what you need while you still have access, drop it, and a later bug or a
compromised dependency cannot read it again. Three details from the documentation matter. Dropping only affects future checks and does
not close anything already open (file descriptors, sockets, child processes, workers). You can only drop what was granted exactly: a
folder grant is dropped by naming that folder, not a file inside it, and a * grant is dropped only without a reference. And dropping
without a reference removes the whole scope.
import { readFileSync } from 'node:fs';
const file = new URL('./data/books.json', import.meta.url);
const dataDir = new URL('./data/', import.meta.url).pathname;
const books = JSON.parse(readFileSync(file, 'utf8')); // startup read
console.log('before drop:', process.permission.has('fs.read', dataDir));
process.permission.drop('fs.read', dataDir); // irreversible
console.log('after drop :', process.permission.has('fs.read', dataDir));
try {
readFileSync(file);
} catch (err) {
console.log('read after drop:', err.code);
}
process.permission.drop('worker'); // drop a whole scope
console.log('title:', books.title);
node --permission --allow-fs-read="$PWD/data" --allow-worker drop.mjs (identical on 26.10.0 and 24.21.0)before drop: true
after drop : false
read after drop: ERR_ACCESS_DENIED
title: Dune
On Node.js 22.23.3 the call fails with a TypeError because drop does not exist. Observed, not documented: dropping a path that is only
inside a granted folder does nothing and has() stays true, so do not assume a narrower drop worked; check with has().
Audit mode: find out what you need
Writing a correct --allow- list by hand is tedious and easy to get wrong. --permission-audit (Node.js 26 and 24.20+) runs the
program with the checks in place but without enforcing them: nothing throws, and each violation is published on a
node:diagnostics_channel channel instead. The flag does not need --permission and the --allow- flags are optional; you can combine
them to audit only the part you have not yet granted. The channels are node:permission-model:fs, :net, :child, :worker,
:inspector, :wasi, :addon and :ffi, and each message is an object with permission and resource.
import diagnosticsChannel from 'node:diagnostics_channel';
import { readFileSync, writeFileSync } from 'node:fs';
const scopes = ['fs', 'net', 'child', 'worker', 'inspector', 'wasi', 'addon', 'ffi'];
for (const scope of scopes) {
diagnosticsChannel.subscribe(`node:permission-model:${scope}`, (msg) => {
console.log(`[audit] ${msg.permission} -> ${msg.resource}`);
});
}
// Neither call throws in audit mode; each one is reported instead.
const text = readFileSync(new URL('./data/books.json', import.meta.url), 'utf8');
writeFileSync(new URL('./out/audit.txt', import.meta.url), text);
console.log('done, bytes:', text.length);
node --permission-audit audit.mjs (26.10.0 and 24.21.0; absolute paths shortened to <app>)[audit] FileSystemRead -> <app>/data/books.json
[audit] FileSystemWrite -> <app>/out/audit.txt
done, bytes: 17
With --allow-fs-read=<app> added, only the write line remained (observed), which is the workflow: run the real workload (start-up, a
request or two, the test suite) under --permission-audit, collect the distinct resource values per channel, turn them into
--allow-* flags, then switch to --permission. A subscriber only sees what happens after it subscribes, so register it at the very top of the entry point (see
Diagnostic Reports, Logging & Observability for the channel
mechanism). On Node.js 22 the flag does not exist.
A discrepancy to be aware of, labelled observed: the documentation says that when both --permission and --permission-audit are
given, --permission wins and the model enforces. On 26.10.0 and 24.21.0 the program that reads data/books.json without a grant ran
to completion with both flags, in either order, behaving as audit mode. Do not rely on mixing the two; use one flag per run and verify
with a denial test.
The configuration file
The same settings can live in a Node.js configuration file instead of a long command line. The file is JSON, named node.config.json
by default, and permission is a top-level namespace next to nodeOptions, test and watch. The general mechanism (path forms,
$schema, nodeVersion, precedence) is in
CLI Options, Environment & Config File; here only the permission block
matters.
{
"permission": {
"allow-fs-read": ["./data"],
"allow-fs-write": ["./out"],
"allow-child-process": false,
"allow-worker": false
}
}
node --experimental-default-config-file probe.mjs
On Node.js 26.10.0 this printed an ExperimentalWarning for the config file and then exactly the second half of the earlier probe
output (read and write allowed, child and worker denied), because, as the documentation says, the presence of the permission namespace
turns --permission on by itself. The details, observed:
| Line | Observed with --experimental-default-config-file and the file above |
|---|---|
Node.js 26.10.0 |
The permission block is applied and |
Node.js 24.21.0 |
Fails with |
Node.js 22.23.3 |
The permission block is not applied: without |
The value types follow the flag: path flags take an array of strings, switches take booleans. Keys cannot repeat across namespaces,
the parser rejects unknown keys, and the documentation warns that Node.js does not validate or sanitise the file, so never load an
untrusted configuration file. Command-line options and NODE_OPTIONS take priority over the file.
Using the model with npx
npx itself must read the package it runs, so npx --node-options="--permission" some-package fails with a FileSystemRead denial.
The documentation gives two fixes: for a globally installed package grant the global node_modules
(npx --node-options="--permission --allow-fs-read=$(npm prefix -g)" some-package), and for the npx cache grant the cache directory
(--allow-fs-read=$(npm config get cache)). The --node-options form sets NODE_OPTIONS for the processes npx spawns, not for npx
itself.
Where the seat belt ends
The documentation lists constraints and known issues. Each row below is one of them, plus the two I demonstrated.
| Limit | Consequence |
|---|---|
Symbolic links are followed, even outside the granted paths; relative links can reach arbitrary files |
Never grant a folder that contains relative symbolic links, and do not extract untrusted archives into a granted folder. |
Other native code is not covered, for example |
The permission page names |
Open file descriptors bypass the model |
Descriptors passed in from outside (or inherited) keep their access. |
Child processes that are not Node.js are not restricted |
|
|
Each warns that granting it can invalidate the model; the OpenSSL STORE loaders can reach files, devices, tokens and the network without being limited by the fs or net scopes. |
|
A restricted process can signal another Node.js process of the same OS user to open its inspector. Use different OS users or OS isolation. |
Early-reading flags ( |
Anything controlled by the operator’s command line is trusted. |
OpenSSL engines and sqlite run-time extensions cannot be requested while the model is on |
A restriction, not a hole, but it breaks setups that depend on them. |
Some operator-chosen output locations are not consistently checked |
For example trace files rotated by |
import { readFileSync } from 'node:fs';
import { DatabaseSync } from 'node:sqlite';
const dataDir = new URL('./data/', import.meta.url);
// 1. A symbolic link inside a granted folder points outside of it.
try {
console.log('via symlink :', readFileSync(new URL('./link.txt', dataDir), 'utf8').trim());
} catch (err) {
console.log('via symlink :', err.code);
}
// 2. node:sqlite opens the database file in native code, outside the fs checks.
try {
const db = new DatabaseSync(new URL('../limits-demo.db', import.meta.url).pathname);
db.exec('CREATE TABLE IF NOT EXISTS t (x)');
console.log('via sqlite : created a database file outside every grant');
db.close();
} catch (err) {
console.log('via sqlite :', err.code);
}
With data/link.txt a symbolic link to a file outside data/ and node --permission --allow-fs-read="$PWD/data" limits.mjs, all three
lines (26.10.0, 24.21.0, 22.23.3) printed the linked file’s contents and created the database file outside every grant (observed). The
sqlite result is a good example of why "the model checks node:fs`" is not the same as "the process cannot touch the disk": a
permission-model run that still imports `node:sqlite has an unchecked path to the file system.
import { spawnSync } from 'node:child_process';
// A non-Node program is not bound by the Node.js permission model at all.
const r = spawnSync('sh', ['-c', 'echo escaped > escaped.txt && echo shell wrote a file'], { encoding: 'utf8' });
console.log(r.stdout.trim() || r.error?.code);
Run with node --permission --allow-fs-read="$PWD/escape.mjs" --allow-child-process escape.mjs on 26.10.0 the shell created
escaped.txt in the working directory even though the Node.js process was granted no write access at all (observed). Without
--allow-child-process the same program is refused with ERR_ACCESS_DENIED. A Node.js child, by contrast, stays restricted: a child
started with process.execPath that tried to write a file was denied on all three lines (observed).
The practical rules that follow:
-
Use the model to reduce accidental authority and to document what a program needs, in addition to, never instead of, containers, dedicated OS users and network policy.
-
Never use it to run code you do not trust. For that, isolate at the OS or hypervisor level; the
vmmodule is not a security boundary either (see Security Best Practices). -
Prefer narrow grants.
--allow-fs-read=plus a narrow--allow-fs-writeis a reasonable first step;--allow-fs-write=and--allow-child-processtogether are close to having no model. -
Combine with the other levers: run as an unprivileged user, drop capabilities in the container, and pass secrets through
--env-file(which is read before the model starts and is outside it). -
Report a bypass you find through the Node.js security process, as the documentation asks.
Bookshelf: a least-privilege recipe
The Bookshelf API from this section (an HTTP server on node:http with an in-memory node:sqlite database) needs very little: read its
own src/ folder and package.json, and open a listening socket. A repeatable way to arrive at such a list is the audit workflow:
run it under --permission-audit, read the reported resources, then enforce exactly those.
# 1. Discover what the program touches (Node.js 26 or 24.20+)
PORT=0 node --permission-audit src/server.js
# 2. Enforce exactly that on Node.js 26; --allow-net exists only there
PORT=0 node --permission \
--allow-fs-read="$PWD/src" --allow-fs-read="$PWD/package.json" \
--allow-net \
src/server.js
Observed on 26.10.0 (with the server started on port 0 so the system picks a free one): the enforced command prints listening <port>,
answers a POST /books and a GET /books normally, and omitting --allow-net makes it stop at start-up with ERR_ACCESS_DENIED and
permission: 'Net'. A server that writes to a database file would additionally need --allow-fs-write for that folder. Validate a real
command by running the test suite under it, and keep it in package.json scripts or the config file so it is reviewed like code. On
Node.js 24 and 22 leave --allow-net out; the flag does not exist there, so sockets are always allowed, which is a meaningful
difference when you decide how much to trust the model on those lines.
What changed recently
| Version | Change |
|---|---|
v20.0.0 |
Introduced as |
v20.7.0 |
Comma-separated path lists are no longer accepted for the fs flags. |
v21.6.0, v20.12.0 |
|
v22.3.0, v20.16.0 |
|
v22.13.0, v23.5.0 |
Declared stable; |
v22.16.0, v23.10.0 |
Node.js gains configuration-file support ( |
v22.17.0, v24.2.0 |
Application entry points and |
v22.18.0, v24.4.0 |
Permitted child processes inherit the model’s flags. |
v24.12.0, v25.0.0 |
|
v25.0.0 |
|
v25.8.0, v24.20.0 |
|
v26.1.0 |
|
v26.7.0, v24.21.0 |
|
v26.9.0 |
|
Where the books are outdated
Efficient Node.js by Samer Buna (O’Reilly, 2025) mentions the permission model only as something being introduced; it is stable now,
and its practical surface is much larger than a single flag: --allow-net (Node.js 25 and later), --allow-inspector, audit mode,
process.permission.drop() and the configuration file did not exist or were not settled when it was written. Node.js Projects by
Jonathan Wexler (O’Reilly, 2025) does not cover the model at all, so its scripts and servers run with full authority; the patterns it
uses (file uploads, child processes, third-party clients) are precisely the places where the model, plus the limits above, is
worth considering. Neither book discusses the seat-belt-versus-sandbox boundary, so the safe reading of this page is the official
documentation, not an older tutorial’s claim that --permission makes untrusted code safe.