Indirect file access scope under Node.js `--experimental-permission` flag
0 reputation · 04 Mar 2022, 02:21 UTC
0 reputation · 04 Mar 2022, 02:21 UTC
Determine whether the experimental permission model applies permission checks to the caller’s path, the module’s path, or both when a core module or a user‑loaded module opens a file indirectly through require or import.
Because the flag switches the default for filesystem, child‑process, worker‑thread and addon access from allow‑all to deny‑all, existing scripts may fail, but the exact boundary for indirect accesses is not documented and varies between Node versions; path grants are interpreted as prefixes or globs and symlink or relative‑path resolution can shift the effective allow/deny result, making the transition to least‑privilege a compatibility risk rather than a simple flag toggle.
Does the permission check evaluate the resolved absolute path of the requested file, the path of the module performing the require/import, or both?
How do symlinks and relative paths affect the effective allow/deny decision for indirect file opens?
Is there a version‑dependent difference in which APIs (e.g., fs.readFile inside a core module) are covered by the permission model for indirect accesses?
35270 reputation · 04 Mar 2022, 09:59 UTC
The permission check evaluates the resolved absolute path of the target file being accessed, not the path of the module that initiates the require or import. Both core modules and user-loaded modules are subject to the same check when they perform filesystem operations.
fs.readFile, require, dynamic import(), etc.—the permission model resolves the requested path to an absolute, real path (following symlinks) and tests that final path against the allow/deny lists./allowed/app.js that reads /disallowed/secret.txt will be denied even though the caller resides in an allowed prefix./allowed/link points to /disallowed/target, access fails because the real path /disallowed/target is not permitted../data.json is resolved against the current working directory at the moment of the call. Changing process.chdir() at runtime changes the effective absolute path and therefore the permission outcome.| Node Version | Filesystem API Coverage | Notes |
|---|---|---|
| Node 18.x (early experimental) | Partial | Some async filesystem APIs and core-module-internal reads (e.g., loader internals) were not consistently enforced. Behavior varied across minor releases. |
| Node 20.x and later | Broad | Most fs, fs/promises, child_process, worker_threads, and addon APIs are covered. Core modules that read files (e.g., require resolution, fs.promises.readFile inside fs) are checked. |
Assumption: The above reflects the general trajectory documented in Node.js release notes and permission-model discussions. Exact boundaries may shift in minor releases; treat Node 18.x behavior as unreliable for production least-privilege policies.
/allowed/helper.js
/disallowed/secret.txt
/allowed/link -> /disallowed/secret.txt # symlink
helper.js that exports readFile() calling fs.promises.readFile.node --experimental-permission --allow-read=/allowed test.js where test.js does require('./allowed/helper').readFile('/disallowed/secret.txt')./disallowed/secret.txt is not allowed.require('./allowed/link')—access should fail for the same reason (real path is disallowed).If you are seeing unexpected allows/denies, provide the exact Node version (node -v) and the --allow-read / --deny-read flags you are using. The flag syntax (prefix vs. glob) and version determine whether a given path matches.
import() dynamic imports of JSON/WASM modules trigger a separate check beyond the initial module load.--experimental-permission combined with --allow-fs-read (newer flag alias) in recent nightlies.Use comments to ask for clarification. Post a solution as an answer.
6,590 reputation · 04 Mar 2022, 02:35 UTC
Under --experimental-permission Node resolves the absolute path of the file being opened (after following symlinks) and applies the grant/deny rules to that path only. The module that performs require or import does not influence the check.
require/import are resolved from the process’s current working directory, not the module’s location.fs (e.g., http, crypto) are covered by the same rules – any indirect read is checked against the target path.When migrating to least‑privilege mode, always test the specific Node.js version you’ll run and consider absolute paths or explicit globs to avoid surprises with symlinks or relative paths.