Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
src: register the --allow-fs-vfs permission flag
Mounting a VFS under the permission model is refused with a message
telling the user to pass --allow-fs-vfs, but that option was never
registered, so `node --allow-fs-vfs` failed with "bad option" and a
virtual file system could not be mounted under --permission at all.

Register it alongside the other permission flags and document it.

Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
  • Loading branch information
pipobscure committed Sep 3, 2026
commit f9081f8825ac0139770cd98d5ce2b781a3d7d425
23 changes: 23 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,26 @@ process.permission.has('fs.read', 'custom-require.js'); // true
process.permission.has('fs.read', 'custom-require-2.js'); // true
```

### `--allow-fs-vfs`
Comment thread
mcollina marked this conversation as resolved.

<!-- YAML
added: REPLACEME
-->

> Stability: 1.1 - Active development

When using the [Permission Model][], a [virtual file system][] cannot be
mounted by default: [`vfs.mount()`][] throws `ERR_INVALID_STATE` unless the
user explicitly passes the `--allow-fs-vfs` flag when starting Node.js.

A mounted VFS serves paths that the file system permissions do not describe,
so mounting one is gated on its own flag rather than on `--allow-fs-read` or
`--allow-fs-write`.

```console
$ node --experimental-vfs --permission --allow-fs-vfs app.js
```

### `--allow-fs-write`

<!-- YAML
Expand Down Expand Up @@ -3906,6 +3926,7 @@ one is included in the list below.
* `--allow-child-process`
* `--allow-ffi`
* `--allow-fs-read`
* `--allow-fs-vfs`
* `--allow-fs-write`
* `--allow-inspector`
* `--allow-net`
Expand Down Expand Up @@ -4619,6 +4640,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`v8.startupSnapshot.addDeserializeCallback()`]: v8.md#v8startupsnapshotadddeserializecallbackcallback-data
[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data
[`v8.startupSnapshot` API]: v8.md#startup-snapshot-api
[`vfs.mount()`]: vfs.md#vfsmount
[asynchronous module customization hooks]: module.md#asynchronous-customization-hooks
[captured by the built-in snapshot of Node.js]: https://github.com/nodejs/node/blob/b19525a33cc84033af4addd0f80acd4dc33ce0cf/test/parallel/test-bootstrap-modules.js#L24
[collecting code coverage from tests]: test.md#collecting-code-coverage
Expand Down Expand Up @@ -4650,4 +4672,5 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[test runner execution model]: test.md#test-runner-execution-model
[timezone IDs]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
[tracking issue for user-land snapshots]: https://github.com/nodejs/node/issues/44014
[virtual file system]: vfs.md
[ways that `TZ` is handled in other environments]: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
13 changes: 13 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,17 @@ process.permission.has('fs.read', 'custom-require.js'); // true
process.permission.has('fs.read', 'custom-require-2.js'); // true
.Ed
.
.It Fl -allow-fs-vfs
When using the Permission Model, a virtual file system cannot be
mounted by default: \fBvfs.mount()\fR throws \fBERR_INVALID_STATE\fR unless the
user explicitly passes the \fB--allow-fs-vfs\fR flag when starting Node.js.
A mounted VFS serves paths that the file system permissions do not describe,
so mounting one is gated on its own flag rather than on \fB--allow-fs-read\fR or
\fB--allow-fs-write\fR.
.Bd -literal
$ node --experimental-vfs --permission --allow-fs-vfs app.js
.Ed
.
.It Fl -allow-fs-write
This flag configures file system write permissions using
the Permission Model.
Expand Down Expand Up @@ -1964,6 +1975,8 @@ one is included in the list below.
.It
\fB--allow-fs-read\fR
.It
\fB--allow-fs-vfs\fR
.It
\fB--allow-fs-write\fR
.It
\fB--allow-inspector\fR
Expand Down
6 changes: 6 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
&EnvironmentOptions::allow_fs_write,
kAllowedInEnvvar,
OptionNamespaces::kPermissionNamespace);
AddOption("--allow-fs-vfs",
"allow mounting a virtual file system when any permissions are set",
BOOL_FIELD(allow_fs_vfs),
kAllowedInEnvvar,
false,
OptionNamespaces::kPermissionNamespace);
AddOption("--allow-addons",
"allow use of addons when any permissions are set",
BOOL_FIELD(allow_addons),
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ class EnvironmentOptions : public Options {
DEFINE_BOOL_FIELD(permission) = false;
DEFINE_BOOL_FIELD(permission_audit) = false;
DEFINE_BOOL_FIELD(allow_addons) = false;
DEFINE_BOOL_FIELD(allow_fs_vfs) = false;
DEFINE_BOOL_FIELD(allow_inspector) = false;
DEFINE_BOOL_FIELD(allow_child_process) = false;
DEFINE_BOOL_FIELD(allow_net) = false;
Expand Down
38 changes: 38 additions & 0 deletions test/parallel/test-permission-dlopen-binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,41 @@ function run(...permissions) {
assert.strictEqual(res.status, 0, res.stderr);
assert.match(res.stdout, /LOADED world/);
}

// The same, reached the way a program actually gets here: an addon inside a
// mounted VFS. Mounting one under the permission model needs --allow-fs-vfs.
{
const mounted = `
const fs = require('fs');
const vfs = require('node:vfs');
const myVfs = vfs.create();
myVfs.writeFileSync('/binding.node',
fs.readFileSync(${JSON.stringify(addonPath)}));
const mountPoint = myVfs.mount();
try {
console.log('LOADED', require(mountPoint + '/binding.node').hello());
} catch (err) {
console.log('DENIED', err.code, err.permission ?? '');
}
`;
const vfsRun = (...permissions) => spawnSync(
process.execPath,
['--experimental-vfs', '--permission', '--allow-fs-read=*',
...permissions, '-e', mounted],
{ encoding: 'utf8' });

const ok = vfsRun('--allow-fs-vfs', '--allow-addons', '--allow-fs-write=*');
assert.strictEqual(ok.status, 0, ok.stderr);
assert.match(ok.stdout, /LOADED world/);

// Mounting itself is refused without --allow-fs-vfs.
const noVfs = vfsRun('--allow-addons', '--allow-fs-write=*');
assert.notStrictEqual(noVfs.status, 0);
assert.match(noVfs.stderr, /ERR_INVALID_STATE/);
assert.match(noVfs.stderr, /--allow-fs-vfs/);

// Mounted, addons allowed, but no write access: the addon is refused.
const noWrite = vfsRun('--allow-fs-vfs', '--allow-addons');
assert.strictEqual(noWrite.status, 0, noWrite.stderr);
assert.match(noWrite.stdout, /DENIED ERR_ACCESS_DENIED FileSystemWrite/);
}
Loading