Join our Newsletter — 33% off our NHI Course
Home› Guides› Node.js Permission Model Guide: Flags, Audit Mode and…
Guide Application Security

Node.js Permission Model Guide: Flags, Audit Mode and Runtime Sandboxing Limits

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 27 October 2026 13 min read
On this page

A Node.js process normally has every right its operating system user has: it can read any file, open any socket, spawn any program and see every environment variable, including the secrets in them. The Node.js Permission Model, enabled with --permission, lets you restrict file system, network, child process, worker, native addon, WASI and inspector access at process start and drop rights at runtime. This guide explains what each flag does, which Node.js release line supports it, how to roll the model out with audit mode, and, just as important, what it does not protect against. It is for Node.js developers, platform engineers and AppSec teams hardening services, CLIs and build tooling. It goes deeper than the Node.js Security Guide; facts are taken from the Node.js documentation for the v24.x and v26.x lines as checked on 27 October 2026.

Key takeaways

  • It is a seat belt, not a sandbox. The Node.js documentation says the model stops trusted code from unintentionally using resources and gives no security guarantees against malicious code, which can bypass it.
  • Stable, but features vary by release line. The core model has been stable since v23.5.0 and v22.13.0; audit mode, permission.drop() and config file support are in v24.x, and --allow-net arrives in the v25 and v26 lines.
  • Start in audit mode. --permission-audit reports would-be denials through node:diagnostics_channel without breaking the application, so you can build an accurate allowlist first.
  • Grant narrowly, then drop after start-up. Allow only the directories, flags and features a service needs, and call process.permission.drop() once configuration and secrets are loaded.
  • Layer it with OS isolation. Symlinks, already-open file descriptors, early-read flags and cross-process inspector signals sit outside the model; containers, separate OS users and seccomp or AppArmor still do the heavy lifting.

How identity and access work in the Node.js runtime

Without the Permission Model, Node.js inherits the authority of the OS account that starts it. Every dependency in node_modules runs with that same authority, which is why one compromised package can read ~/.npmrc, cloud credential files or environment variables (MITRE ATT&CK T1552.001 Credentials In Files). The Permission Model narrows that authority for the whole process. It does not separate one module from another: all code in the process shares the same grants.

ScopeFlag to grantAdded (per Node.js docs)Stability note
Enable model (enforce)--permissionv20.0.0; stable in v23.5.0 and v22.13.0Stable
Audit mode--permission-auditv25.8.0 and v24.20.0Enforce wins if both are set
File system read / write--allow-fs-read, --allow-fs-writev20.0.0; entrypoints implicitly readable from v24.2.0 and v22.17.0Stable
Child processes--allow-child-processv20.0.0; flags passed to children via NODE_OPTIONS from v24.4.0 and v22.18.0Active development
Worker threads--allow-workerv20.0.0
Native addons--allow-addonsv21.6.0 and v20.12.0
WASI--allow-wasiv22.3.0 and v20.16.0
Network--allow-netv25.0.0 (present in v26.x docs, not v24.x)Active development
Inspector--allow-inspectorv25.0.0 and v24.12.0
FFI--allow-ffiv26.1.0Only in builds with FFI support
OpenSSL STORE loaders--allow-openssl-storev26.7.0 and v24.21.0Grants broad authority (see below)
Environment variables--allow-envDocumented on the main branch, not yet in a releaseCheck before relying on it

Release lines matter for planning. The Node.js release schedule shows v24 moving to maintenance on 20 October 2026, v26 becoming LTS on 28 October 2026, and v22 in maintenance until 30 April 2027. The older experimental policy feature (--experimental-policy, integrity manifests for modules) was removed in v22.2.0, so the Permission Model is the only built-in runtime restriction mechanism today.

What the Node.js project treats as a vulnerability

The Node.js Security Policy states the model is designed to reduce the blast radius of mistakes in trusted application code, not to act as a security boundary against intentional misuse or a compromised process. It triages Permission Model reports in three lanes, which tells you what you can rely on:

LaneMeaningPractical consequence
VulnerabilitySomething Node.js does not trust crosses a permission check without trusted code already running in the processFixed as a security issue
Security-interest bugTrusted code uses stable APIs as intended but an invariant is enforced inconsistently, for example one API checks and an equivalent API does notFixed as hardening, not automatically a CVE; keep Node.js patched
ExcludedIntentional misuse by code already in the process, operator-selected flags, modified execArgv or env, or expecting a sandbox for malicious same-process codeNot fixed; you must control these yourself

The same policy lists items that are not vulnerabilities: files Node.js writes at operator-chosen locations (such as trace files from --trace-event-file-pattern), SQL-level file access through node:sqlite (for example ATTACH DATABASE), symlinks that resolve to allowed paths, and workers started with modified execArgv or env. It concludes that applications relying on the model must not grant --allow-worker to code they do not trust.

Enforce and audit modes

  • Enforce mode (--permission): any operation without a grant throws ERR_ACCESS_DENIED with a permission property such as FileSystemRead and the resource path.
  • Audit mode (--permission-audit): checks run, nothing is denied, and each violation is published on a diagnostics channel: node:permission-model:fs, :net, :child, :worker, :inspector, :wasi, :addon and :ffi. Audit can be combined with --allow-* flags to audit only part of the surface.

Admin roles and privileged access

The Permission Model has no users or roles of its own. Privilege is decided by whoever controls the process launch: the command line, NODE_OPTIONS, and the configuration file. Treat those as privileged configuration.

  • Command line and service units: the flags in a Dockerfile CMD, a systemd unit or a Kubernetes container spec define the grants. Changes to them should go through the same review as IAM policy changes.
  • Configuration file: with --config-file, permissions live in a top-level permission object (for example "allow-fs-read": ["./foo"], "allow-child-process": true), and its presence enables --permission automatically. Because the file usually lives in the project, someone who can change the repository can widen grants; protect it with code owners and review.
  • Inspector access: enabling the inspector is restricted under the model (the SIGUSR1 listener is not created). Keep --allow-inspector out of production; a debugger attached to a process can do anything the process can.
  • Runtime self-restriction: process.permission.drop(scope[, reference]) is irreversible. It only affects future checks: open file descriptors, sockets, child processes and workers stay usable, so close them yourself. You can drop only exactly what was granted; a wildcard grant can only be dropped as a whole scope.

Integrations, API tokens and service accounts

The point of the model for most teams is protecting the credentials a service holds. Three patterns work well:

  1. Read secrets, then drop access: read /etc/myapp/config.json or a mounted secret file at start-up, then call process.permission.drop('fs.read', '/etc/myapp'). A later bug or injected code path can no longer reread the file through node:fs.
  2. Deny child processes in services: most web services never need to spawn programs. Leaving --allow-child-process off blocks a common post-exploitation step and accidental shell-outs; when children are allowed, from v24.4.0 and v22.18.0 the parent's permission flags are passed to Node.js children through NODE_OPTIONS.
  3. Restrict network where your line supports it: on v25 and later, omitting --allow-net stops a build script or report generator from calling out. In v26.x it is an all-or-nothing switch, so egress filtering at the network layer is still needed for allowlisting destinations.

Environment variables deserve a specific warning. The --allow-env flag and the behaviour of stripping unlisted variables at start-up are documented on the Node.js main branch but carry no release version yet, so on v24.x and v26.x a restricted process can still read process.env. Until that ships in your line, prefer file-based secrets you can drop, or a secrets manager call made once at start-up. See the Secrets Management Guide.

Two integrations widen authority beyond what the flags suggest. --allow-openssl-store lets OpenSSL STORE loaders (for example a PKCS#11 provider used by crypto.createPrivateKey()) access files, devices, tokens or the network, and that access is not constrained by the fs or net scopes. Native addons and FFI run native code that the model cannot inspect, so --allow-addons and --allow-ffi effectively remove the seat belt for anything that code does.

Using the model with npx and package tooling

To run a package executable under the model, pass flags through npx: npx --node-options="--permission --allow-fs-read=$(npm config get cache)" package-name, which sets NODE_OPTIONS for spawned Node.js processes without affecting npx itself. This is a useful way to run one-off CLIs from the registry with read-only access. It does not make install scripts safe: lifecycle scripts are a package manager concern covered in the npm Install-Time Security Guide.

Logging and detection

SignalHow to captureWhat it tells you
Audit violationsSubscribe to node:permission-model:* channels and forward permission and resource to logsThe exact grants a service needs; unexpected resources in production audit runs
ERR_ACCESS_DENIED errorsError handler and APM; alert on new permission/resource pairsRegressions after upgrades, or code (including dependencies) trying something new
Runtime grants checkprocess.permission.has('child') etc. in a health endpointThe process really started with the intended grants
Launch configuration driftPolicy-as-code checks on Dockerfiles, Helm charts and node.config.jsonSomeone widened a grant, for example --allow-fs-write=*

Metrics that show the control is working:

  • Percentage of production Node.js services running with --permission in enforce mode.
  • Services with wildcard file system grants (*) or --allow-child-process (falling).
  • Services that drop secret file access after start-up.
  • Distinct audit violations per service before enforcement (should converge to zero).
  • Mean time to triage a new ERR_ACCESS_DENIED pattern.

Hardening baseline

Rollout

  1. Pick the release line: confirm which flags your Node.js version supports using the table above; plan audit mode on v24.20.0 or later.
  2. Audit in staging: run with --permission-audit and a subscriber logging every violation through realistic test traffic and batch jobs.
  3. Write the allowlist: turn findings into explicit paths, for example --allow-fs-read=/app --allow-fs-read=/etc/myapp --allow-fs-write=/tmp/myapp/, avoiding *.
  4. Enforce in a canary: switch one instance to --permission, watch ERR_ACCESS_DENIED rates, then widen the ring.
  5. Add runtime drops: drop secret paths and the child scope after initialisation.
  6. Roll back: revert to audit mode by flag change only; do not remove the subscriber, so you keep visibility.

Path rules worth knowing

  • Entrypoints: the main script and -r preloads are readable without a grant on current lines.
  • Wildcards: anything after * is ignored, so /home/*.js behaves like /home/*. Existing directories get an implicit trailing wildcard; for a directory that does not exist yet, add /* yourself.
  • Comma-separated paths: no longer accepted since v20.7.0; repeat the flag instead.
  • Descriptor operations: fsync, fdatasync, fchmod, fchown and futimes are disabled under the model because they cannot be tied to a path; libraries that rely on them will fail.

Where it sits next to other controls

ControlGranularityStops malicious code?Use for
Node.js Permission ModelProcess, by resource type and pathNo (by design)Least privilege for trusted code, limiting mistakes
Container with read-only root, non-root userProcess tree, file systemPartlyBaseline for every service
seccomp, AppArmor or SELinux profileSystem calls and filesStronger, kernel enforcedUntrusted or high-risk workloads
Network policy or egress proxyDestinationsYes for networkAllowlisting outbound hosts
Separate process or VM per tenantWhole workloadStrongestRunning customer-supplied code

Deno exposes similar permission flags with different defaults; see the Deno and Bun Runtime Security Guide for a comparison.

Common misconfigurations

  • Treating it as a sandbox for untrusted code: the documentation is explicit that malicious code can bypass the model. Plugins, user scripts or AI-generated code need OS or VM isolation.
  • Granting * to make errors go away: --allow-fs-read=* --allow-fs-write=* leaves only the child, worker and addon restrictions.
  • Relative symbolic links in granted paths: symlinks are followed even outside granted paths, and relative symlinks may expose arbitrary files. Ensure no granted directory contains them.
  • Workers launched with custom execArgv: a worker with execArgv set, even to [], does not keep the parent's grants unless the flags are listed again.
  • Forgetting pre-initialisation inputs: --env-file, --openssl-config and V8 flags set through v8.setFlagsFromString are processed outside the model; already-open file descriptors also bypass it.
  • Assuming other file paths are covered: the model guards node:fs; the documentation notes it does not guarantee access through other means such as node:sqlite, and run-time loadable SQLite extensions are blocked.
  • Same OS user for sandboxed and sensitive processes: process._debugProcess(pid) is not gated, so a restricted process can force another Node.js process of the same user to open its inspector. Run them as different users.

Framework mapping

Control areaNIST SP 800-53 Rev. 5NIST CSF 2.0Other
Least privilege for runtimeAC-6, CM-7PR.PS-01OWASP ASVS 5.0 V15.2.5
Process isolation (layered)SC-39PR.PS-01ATT&CK T1059.007 JavaScript
Protecting credentials on diskAC-6PR.AA-05ATT&CK T1552.001, T1005
Dependency riskCM-7(5)PR.PS-06ASVS V15.1.2, V15.1.5; ATT&CK T1195.001
Audit mode and violation loggingAU-12DE.CM-09ISO/IEC 27001:2022 A.8.15

Practitioner checklist

  • Record which Node.js line each service runs and which permission flags it supports.
  • Run every service through --permission-audit with a diagnostics channel subscriber before enforcing.
  • Enforce --permission in production with explicit paths and no * grants.
  • Leave --allow-child-process, --allow-addons, --allow-ffi and --allow-inspector off unless justified in writing.
  • Drop secret file paths and unused scopes with process.permission.drop() after start-up, and close open handles.
  • Protect node.config.json, Dockerfiles and service units with code owners.
  • Check granted directories for relative symlinks.
  • Keep containers non-root with read-only file systems and egress controls; run untrusted code in separate OS users or VMs.
  • Alert on new ERR_ACCESS_DENIED permission and resource pairs.

Standards and references

Related NHI Mgmt Group resources: Node.js Security Guide · npm Tokens Security Guide · Malicious Package and Dependency Confusion Guide · Secrets Management Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 27 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org