
Node.js Permission Model: Security Gotchas Developers Hit in 2026
Node.js permission model, updated Oct 2026: all 14 bypass CVEs fixed this year, audit mode, permission.drop(), worker rules and safe versions to run.
- What: The Node.js permission model restricts file system, process, worker and other access for a running Node.js process.
- Why it matters: Node.js fixed 14 permission model vulnerabilities across five security releases in 2026, two of them high severity.
- What to do: Run Node.js 22.23.4, 24.22.1 or 26.12.1 or later, and never treat the model as a sandbox.
- Key stat: Node.js 20 and 25 are listed as end-of-life in the release table, with final releases in March 2026.
The Node.js permission model is an opt-in --permission flag that denies file system, child process, worker, addon, WASI and inspector access unless you grant it. It is stable since v23.5.0 and v22.13.0. It protects trusted code from mistakes, not from malicious code. In 2026, Node.js patched 14 bypass bugs, so stay on the latest LTS patch.
The Node.js permission model is a process-level mechanism for restricting access to specific resources during execution, enabled with the --permission flag. It was added in Node.js v20.0.0 and marked stable in v23.5.0 and v22.13.0. The official documentation describes it as a “seat belt” for trusted code that does not provide security guarantees in the presence of malicious code. Source: Node.js Permissions documentation.
The Node.js permission model changed a lot in 2026, and older guides are now out of date. Node.js published five security releases this year that included permission model fixes. Two release lines went end-of-life. New features arrived: an audit mode, a way to drop permissions at runtime, and new grant flags. The documented rules for worker threads also differ from what many tutorials say.
This guide is updated to the state of Node.js on October 28, 2026. Every version number and CVE below links to the official Node.js source. It covers what the model restricts, every 2026 permission model CVE, the new APIs, the worker thread rules, and the limitations the docs still list.
What does the Node.js permission model restrict?
With --permission enabled, the Node.js permission model restricts file system access through node:fs, network, child processes, worker threads, native addons, WASI, FFI, the inspector, and OpenSSL STORE loaders. That is the list in the current permissions documentation for v26.12.1. Anything you do not grant throws an ERR_ACCESS_DENIED error with the permission name and resource.
You grant access with flags. The main ones are --allow-fs-read, --allow-fs-write, --allow-child-process, --allow-worker, --allow-addons and --allow-wasi. Your entry file, and any file passed with -r, is added to the read allowlist automatically.
node \
--permission \
--allow-fs-read=/app/src \
--allow-fs-read=/app/node_modules \
--allow-fs-write=/app/logs/ \
server.js
Three grant flags are newer and still marked “Stability: 1.1 – Active development” in the CLI documentation:
--allow-net, added in v25.0.0.--allow-ffi, added in v26.1.0, for thenode:ffimodule.--allow-openssl-store, added in v26.7.0 and v24.21.0. The docs warn that access performed by a STORE loader is not constrained by thefs.read,fs.writeornetscopes.
If you run 22.x or 24.x LTS, read the permissions page for your exact version before relying on network restrictions, because --allow-net is listed as added in v25.0.0.
How do Node.js permission model path grants work?
File system grants accept *, absolute paths, and paths relative to the working directory. A trailing wildcard matches everything with that prefix. Per the documentation, characters after * are ignored, so /home/*.js behaves like /home/*.
One rule catches people out. When the model starts, it adds a wildcard to a granted directory only if that directory already exists. For a folder created at runtime, write the wildcard yourself:
# /app/uploads does not exist yet at startup
node --permission --allow-fs-write=/app/uploads/* server.js
Also note the documented behaviour for descriptor-based calls. Operations that act on an already-open file descriptor, such as fsync, fdatasync, fchmod, fchown and futimes, throw ERR_ACCESS_DENIED when the model is enabled. This includes the matching FileHandle methods. If your code calls fsync to flush writes, test it under --permission before deploying.
Which Node.js permission model vulnerabilities were fixed in 2026?
Node.js fixed 14 permission model vulnerabilities in 2026, in the January, March, June, July and October security releases. Two were rated high, two medium and ten low. The table lists each one with the affected lines stated by Node.js.
Node.js permission model CVEs, 2026
| CVE | Severity | What it bypassed | Affected lines | Release |
|---|---|---|---|---|
| CVE-2025-55130 | High | fs read/write limits via crafted relative symlinks | 20, 22, 24, 25 | Jan 13 |
| CVE-2026-21636 | Medium | Network limits via Unix domain socket connections | 25 | Jan 13 |
| CVE-2025-55132 | Low | Read-only limit via fs.futimes() timestamp changes |
20, 22, 24, 25 | Jan 13 |
| CVE-2026-21711 | Medium | Network limits via UDS server bind/listen | 25 | Mar 24 |
| CVE-2026-21715 | Low | Read limits via fs.realpathSync.native() |
20, 22, 24, 25 | Mar 24 |
| CVE-2026-21716 | Low | Write limits via FileHandle.chmod()/chown() |
20, 22, 24, 25 | Mar 24 |
| CVE-2026-48617 | Low | Path validation in process.report.writeReport() |
22, 24, 26 | Jun 18 |
| CVE-2026-48935 | Low | Read-only limit via FileHandle.utimes() |
22, 24, 26 | Jun 18 |
| CVE-2026-48936 | Low | Network limits via UDS server (incomplete fix of CVE-2026-21636) | 26 | Jun 18 |
| CVE-2026-58043 | High | fs allowlist over-grant across radix-tree prefix boundaries | 22, 24, 26 | Jul 29 |
| CVE-2026-56847 | Low | Write limits via trace events | 22, 24, 26 | Jul 29 |
| CVE-2026-58039 | Low | Write limits via process.report |
22, 24, 26 | Jul 29 |
| CVE-2026-106071 | Low | Wildcard grant over-match in radix lookup | 26 | Oct 21 |
| CVE-2026-106073 | Low | Read limits via fs.realpathSync() (incomplete fix of CVE-2026-21715) |
22, 24, 26 | Oct 21 |
A pattern stands out. Several fixes were follow-ups to earlier ones. Node.js describes CVE-2026-48936 as an incomplete fix of CVE-2026-21636. It describes CVE-2026-106073 as an incomplete fix of CVE-2026-21715. CVE-2026-21716 is an incomplete fix of CVE-2024-36137. Treat each permission model fix as one step, not the final word, and keep patching.
Which Node.js version should you run with –permission?
Run the latest patch of a supported line. The October 21, 2026 security release shipped Node.js 22.23.4, 24.22.1 and 26.12.1. The release table shows v24 and v22 as LTS and v26 as Current. It lists v20 and v25 as end-of-life, with their last updates on March 24 and March 31, 2026.
End-of-life lines are affected by every later security release, as the Node.js advisories note. So if you use the permission model on Node.js 20 or 25, you are missing at least the June, July and October fixes. For production, the release page says to use Active LTS or Maintenance LTS releases. If you are moving off an old line, our Node.js 24 LTS upgrade guide covers the main breaking changes.
What are permission audit mode and permission.drop()?
Audit mode lets you find required grants without breaking the app. You enable it with --permission-audit, listed in the CLI docs as added in v25.8.0 and v24.20.0. Checks still run, but access is not denied. Each violation is published on a node:diagnostics_channel channel.
const dc = require('node:diagnostics_channel');
dc.channel('node:permission-model:fs').subscribe((msg) => {
console.log(`would deny: ${msg.permission} on ${msg.resource}`);
});
The channels are node:permission-model:fs, net, child, worker, inspector, wasi, addon and ffi. If you pass both flags, --permission takes precedence and enforces.
process.permission.drop() removes a permission at runtime, and the removal is irreversible. The process docs list it as added in v26.3.0 and v24.20.0, at “Stability: 1.1”. A common use is reading secrets at startup, then dropping access:
const fs = require('node:fs');
const config = fs.readFileSync('/etc/myapp/config.json', 'utf8');
// Must match the original grant exactly
process.permission.drop('fs.read', '/etc/myapp');
process.permission.drop('child'); // drop a whole scope
Two rules from the documentation matter here. Dropping does not close resources that are already open, such as file descriptors, sockets, child processes or workers. You can also only drop exactly what was granted. A directory grant cannot be narrowed to one file, and a * grant can only be dropped as a whole scope.
Do worker threads inherit the Node.js permission model?
The documented answer is more nuanced than many older guides suggest. Under --permission, creating a worker is itself restricted until you pass --allow-worker. A default Worker with no execArgv option receives the parent’s CLI flags, including --permission and any --allow-* grants.
The catch is execArgv. Per the constraints section, setting execArgv explicitly, even to [], replaces the inherited flags. The worker then does not keep the parent’s grants unless you list those flags again. Node.js calls this intended behaviour, not a bypass.
const { Worker } = require('node:worker_threads');
// Receives the parent's --permission and --allow-* flags
new Worker('./task.js');
// Replaces inherited flags: the parent's grants are not kept
new Worker('./task.js', { execArgv: [] });
My recommendation: only pass --allow-worker when you control every place a worker is created, and audit any code that sets execArgv.
What does the Node.js permission model still not protect?
The current documentation lists several limits that remain true in v26.12.1. Read them before you design around the model.
- Symlinks. Symbolic links are followed even outside granted paths, and relative symlinks may allow access to arbitrary files. The docs say to ensure no granted path contains relative symlinks.
- Early file reads. Flags such as
--env-fileand--openssl-configread files before the model initialises, so they are not checked. The same applies to V8 flags set withv8.setFlagsFromString. - Existing descriptors. Using existing file descriptors through
node:fsbypasses the model. - SQLite. The model does not guarantee that the file system cannot be reached through
node:sqlite. Run-time loadable SQLite extensions are blocked. - OpenSSL engines. They cannot be requested at runtime, which affects
crypto,httpsandtls. - Cross-process inspector.
process._debugProcess(pid)is not gated by any scope. A restricted process can force another Node.js process run by the same OS user to open its inspector. The docs recommend separate OS users or OS-level isolation.
These limits are why the model is a seat belt, not a sandbox. For application-layer security, our guide to Next.js 16 auth security mistakes covers a different layer of the same problem.
How do you configure the Node.js permission model in a config file?
You can declare permissions under a permission key in node.config.json. When that key is present, Node.js enables --permission automatically. This example is adapted from the documentation example:
{
"permission": {
"allow-fs-read": ["./src"],
"allow-fs-write": ["./logs"],
"allow-child-process": false,
"allow-worker": false,
"allow-net": true
}
}
Check the flag name for your version. The CLI docs list --config-file as added in v23.10.0 and v22.16.0, originally named --experimental-config-file. The source docs note a rename to --config-file, with the old names kept as aliases. If your version rejects one name, try the other. For npx, the docs show granting read access with --allow-fs-read=$(npm prefix -g) or $(npm config get cache).
Node.js permission model features by release line
| Feature | 22.x LTS | 24.x LTS | 26.x Current |
|---|---|---|---|
Stable --permission |
Yes (22.13.0+) | Yes | Yes |
--permission-audit |
Not listed | 24.20.0+ | Yes |
process.permission.drop() |
Not listed | 24.20.0+ | 26.3.0+ |
--allow-openssl-store |
Not listed | 24.21.0+ | 26.7.0+ |
--allow-ffi |
Not listed | Not listed | 26.1.0+ |
| Patched for all 2026 CVEs on its line | 22.23.4 | 24.22.1 | 26.12.1 |
“Not listed” means the CLI or process docs do not list that line in the “added” versions. Confirm with node --help on your build.
The NexGismo verdict: Turn on the permission model for services you own, and use audit mode first if you are on 24.20.0 or later. Pin to the latest patch, because 2026 showed fixes arriving in several rounds for the same bug class. Never use it to contain untrusted code. For that, use containers, separate OS users, or a VM.
- The Node.js permission model is enabled with
--permission, was added in v20.0.0, and has been stable since v23.5.0 and v22.13.0. - Node.js fixed 14 permission model vulnerabilities in 2026 across the January, March, June, July and October security releases, two rated high severity.
- The October 21, 2026 security release shipped Node.js 22.23.4, 24.22.1 and 26.12.1; Node.js 20 and 25 are end-of-life and receive no fixes.
--permission-auditlogs would-be denials tonode:diagnostics_channelwithout blocking, and is listed as added in v25.8.0 and v24.20.0.- A default
Workerinherits the parent’s--permissionflags, but settingexecArgvexplicitly replaces them; creating workers needs--allow-worker. - The documentation still lists symlinks, existing file descriptors,
node:sqliteandprocess._debugProcess()as limits the model does not cover.
Frequently Asked Questions
Is the Node.js permission model a sandbox for untrusted code?
No. The official documentation calls it a "seat belt" for trusted code. It stops your own code from accidentally touching files or resources you did not grant. The docs state it does not provide security guarantees against malicious code, which can bypass it. Use OS-level isolation such as containers or separate OS users for untrusted code.
How do I enable the Node.js permission model?
Start Node.js with the –permission flag, then grant access explicitly with flags such as –allow-fs-read, –allow-fs-write, –allow-child-process and –allow-worker. Without grants, file system access, child processes, worker threads, native addons, WASI and the inspector are restricted. Your entry file is added to the read allowlist automatically.
What is Node.js permission audit mode?
Audit mode, enabled with –permission-audit, performs permission checks without denying access. Each violation is published to a node:diagnostics_channel channel such as node:permission-model:fs, so you can log what enforce mode would block. The CLI docs list it as added in v25.8.0 and v24.20.0. If both flags are set, –permission wins.
Do worker threads inherit Node.js permission model restrictions?
Partly. Under –permission, creating a worker requires –allow-worker. A default Worker with no execArgv option receives the parent's CLI flags, including –permission and –allow-* grants. If you set execArgv yourself, even to an empty array, the worker does not keep the parent's grants unless you list those flags again. The docs call this intended, not a bypass.
Which Node.js versions fix the 2026 permission model vulnerabilities?
The latest security release, published October 21, 2026, shipped Node.js 22.23.4, 24.22.1 and 26.12.1. Those versions include the permission model fixes from the January, March, June, July and October 2026 releases for their lines. Node.js 20 and 25 are end-of-life, so they receive no further security fixes.
The Node.js permission model is more capable than it was a year ago. Audit mode makes adoption safer, and permission.drop() lets a service shed access after startup. The 2026 CVE record is the other half of the story. Fourteen fixes, several of them follow-ups, show the boundary is still being hardened. Use the model to catch mistakes in code you trust, keep to the latest LTS patch, and isolate anything you do not trust at the OS level. Have a question about a specific flag? Drop a comment below, or subscribe to NexGismo for updates when Node.js publishes its next security release.
Sources
- Node.js — Permissions documentation (v26.12.1)
- Node.js — Command-line options documentation
- Node.js GitHub — cli.md (version history for permission flags)
- Node.js GitHub — process.md (process.permission.drop history)
- Node.js — January 13, 2026 security releases
- Node.js — March 24, 2026 security releases
- Node.js — June 18, 2026 security releases
- Node.js — July 29, 2026 security releases
- Node.js — October 21, 2026 security releases
- Node.js — Release schedule and end-of-life table