Enhanced Security in Node.js v20: The New Permission Model
A Node process inherits the user's full filesystem and process-spawn rights by default, which means any third-party dependency you install can read your ~/.ssh directory or shell out to curl. Node's Permission Model — --permission, introduced as --experimental-permission in Node 20 and renamed in Node 22 — narrows that surface. The model itself is still Stability 1 (experimental), but the flag is no longer prefixed.
Bun has no permission model as of late 2025; for sandboxing on Bun you're back to OS-level isolation (containers, nsjail, sandbox-exec).
What it covers
| Resource | Flag |
|---|---|
| Filesystem read | --allow-fs-read=<path> |
| Filesystem write | --allow-fs-write=<path> |
child_process | --allow-child-process |
worker_threads | --allow-worker |
| WASI | --allow-wasi |
| Native addons | --allow-addons |
Notably absent: network access. Node has no --allow-net equivalent. If isolating outbound HTTP matters, that gap is the headline.
Enabling it
1node --permission index.jsEverything is denied by default. Grant only what the script needs. Quote glob characters on zsh — bare --allow-fs-read=* gets expanded by the shell:
1node --permission --allow-fs-read='*' --allow-fs-write='/tmp/' index.jsFor path-scoped grants:
1node --permission --allow-fs-read=/home/me/project --allow-fs-write=/tmp index.jsChecking permissions at runtime
1process.permission.has('fs.write') // true if any write was granted
2process.permission.has('fs.write', '/tmp/foo.log') // true if /tmp/ matches a granted prefixUseful for libraries that want to degrade gracefully under restricted permissions.
How it stacks up against Deno
Deno shipped with permissions from day one, so they've had years longer to mature.
| Node 22+ Permission Model | Deno 2 | |
|---|---|---|
| Default state | Sandbox with --permission | Sandbox always |
| Filesystem | --allow-fs-read, --allow-fs-write | --allow-read, --allow-write |
| Child processes | --allow-child-process | --allow-run |
| Worker threads | --allow-worker | not separately gated |
| Native code | --allow-addons, --allow-wasi | --allow-ffi (was --allow-plugin pre-1.10) |
| Network | not gated | --allow-net=host:port |
| Environment vars | not gated | --allow-env |
| Module loading | not gated | --allow-import (Deno 2) |
| Stability | Stability 1 (experimental) | Stable |
Deno 2 also dropped --allow-hrtime — high-resolution time is no longer permission-gated.