nodejs/node
Permission Model
Owners: @nodejs/security-wg. Public docs at doc/api/permissions.md.
Purpose
Provide a process-wide capability sandbox that restricts what a Node process can do at runtime. When --permission is on, every privileged operation passes through a centralized check that consults a Permission table on the Environment. Without the right grant, the operation throws ERR_ACCESS_DENIED.
Directory layout
src/permission/
permission.{cc,h} // central Permission registry
permission_base.h // PermissionBase abstract class
addon_permission.{cc,h} // dlopen/dlsym for addons
child_process_permission.{cc,h} // spawn/exec*
ffi_permission.{cc,h} // FFI dlopen
fs_permission.{cc,h} // fs read/write paths
inspector_permission.{cc,h} // inspector enable
net_permission.{cc,h} // outbound/inbound
wasi_permission.{cc,h} // WASI calls
worker_permission.{cc,h} // new Worker(...)
lib/internal/process/permission.js // JS bridge (process.permission.has, .deny)Key abstractions
| Type / file | Role |
|---|---|
Permission (permission.cc) |
Per-Environment registry of PermissionScope → PermissionBase. |
PermissionBase (permission_base.h) |
Abstract base for each domain; subclasses implement Apply / is_granted. |
PermissionScope (permission.h) |
Enum of domains (FileSystemRead, FileSystemWrite, ChildProcess, ...). |
THROW_IF_INSUFFICIENT_PERMISSIONS (macro) |
The macro every privileged code path uses to gate access. |
process.permission (lib/internal/process/permission.js) |
JS-side has/deny API. |
How it gates a call
graph LR
JSC["JS: fs.readFile('/etc/passwd', cb)"] --> JSGate["process.permission.has?"]
JSGate --> Bind[fs binding]
Bind --> Macro[THROW_IF_INSUFFICIENT_PERMISSIONS]
Macro --> Reg[Environment::permission]
Reg --> Domain[FsPermission::is_granted]
Domain -- denied --> Throw[ERR_ACCESS_DENIED]
Domain -- granted --> Op[uv_fs_open]The macros in src/permission/permission.h come in synchronous, async-callback, and return-error variants. Every C++ binding that performs a privileged operation must call the appropriate macro before doing the operation; new bindings are reviewed for this.
CLI surface
| Flag | Purpose |
|---|---|
--permission |
Enable the model. |
--allow-fs-read=<path> |
Add a glob / path to the FS-read allow list. |
--allow-fs-write=<path> |
Add a glob / path to the FS-write allow list. |
--allow-net=<host:port> |
Add a network allow list entry. |
--allow-net-server |
Allow server-side listen. |
--allow-net-client |
Allow outbound connections. |
--allow-child-process |
Permit child_process.spawn/exec. |
--allow-worker |
Permit new Worker(...). |
--allow-wasi |
Permit node:wasi. |
--allow-ffi |
Permit FFI dlopen. |
--allow-addons |
Permit native addon loading. |
--allow-inspector |
Permit attaching the inspector. |
--permission-deny=<scope> |
Deny a previously-granted scope. |
By default --permission denies every domain except where the runtime cannot avoid it (snapshot deserialization, internal startup). The full default policy is in src/permission/permission.cc.
FS path matching
The FS domain uses an interval tree (FsPermission::Tree in fs_permission.cc) to match path globs efficiently, including ** and brace expansions. It also normalizes paths to absolute, resolves .. away, and follows POSIX/Win casing rules.
Integration points
fs:THROW_IF_INSUFFICIENT_PERMISSIONS(env, kFileSystemRead, path)is sprinkled insrc/node_file.cc.child_processandworker_threads: gated atSpawn/Worker::New.inspector:Agent::Startchecks the inspector domain.- JS-side checks:
process.permission.has('net.client'),process.permission.has('fs.read', '/some/path'), etc. - Diagnostics channel:
node:permissionevents fire when access is denied (so monitoring can capture sandbox escapes).
Entry points for modification
- New domain? Add
<name>_permission.{cc,h}undersrc/permission/, register it inpermission.cc, expose it throughprocess.permissioninlib/internal/process/permission.js, and document atdoc/api/permissions.md. - New gating point? Wrap the privileged operation in
THROW_IF_INSUFFICIENT_PERMISSIONS(or the async variant). Add a regression test undertest/parallel/test-permission-*.js. - CLI flag? Edit
src/node_options.ccplus the corresponding domain's setup.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.