Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
28 changes: 28 additions & 0 deletions doc/api/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,34 @@ does not exist, the wildcard will not be added, and access will be limited to
yet, make sure to explicitly include the wildcard:
`/my-path/folder-do-not-exist/*`.

#### Configuration file support

In addition to passing permission flags on the command line, they can also be
declared in a Node.js configuration file when using the experimental
\[`--experimental-config-file`]\[] flag. Permission options must be placed inside
the `permission` top-level object.

Example `node.config.json`:

```json
{
"permission": {
"allow-fs-read": ["./foo"],
"allow-fs-write": ["./bar"],
"allow-child-process": true,
"allow-worker": true,
"allow-net": true,
"allow-addons": false
}
}
```

Run with the configuration file:

```console
$ node --permission --experimental-default-config-file app.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we enable --permission by default if the "permission" object exists in the config-file? So users won't need to pass --permission.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the behaviour of the already present flags it should work out of the box! 🚀

Copy link
Member Author

@marco-ippolito marco-ippolito Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah its would be an improvement we can do it in a followup, that also might require us to review the flags we support. For example --test is not supported in the config file but --watch is (I think its just a discrepancy). We have to standardize the behavior

```

#### Using the Permission Model with `npx`

If you're using [`npx`][] to execute a Node.js script, you can enable the
Expand Down
52 changes: 52 additions & 0 deletions doc/node-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,58 @@
},
"type": "object"
},
"permission": {
"type": "object",
"additionalProperties": false,
"properties": {
"allow-addons": {
"type": "boolean"
},
"allow-child-process": {
"type": "boolean"
},
"allow-fs-read": {
"oneOf": [
{
"type": "string"
},
{
"items": {
"type": "string",
"minItems": 1
},
"type": "array"
}
]
},
"allow-fs-write": {
"oneOf": [
{
"type": "string"
},
{
"items": {
"type": "string",
"minItems": 1
},
"type": "array"
}
]
},
"allow-inspector": {
"type": "boolean"
},
"allow-net": {
"type": "boolean"
},
"allow-wasi": {
"type": "boolean"
},
"allow-worker": {
"type": "boolean"
}
}
},
"testRunner": {
"type": "object",
"additionalProperties": false,
Expand Down
30 changes: 22 additions & 8 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -603,35 +603,49 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
AddOption("--allow-fs-read",
"allow permissions to read the filesystem",
&EnvironmentOptions::allow_fs_read,
kAllowedInEnvvar);
kAllowedInEnvvar,
OptionNamespaces::kPermissionNamespace);
AddOption("--allow-fs-write",
"allow permissions to write in the filesystem",
&EnvironmentOptions::allow_fs_write,
kAllowedInEnvvar);
kAllowedInEnvvar,
OptionNamespaces::kPermissionNamespace);
AddOption("--allow-addons",
"allow use of addons when any permissions are set",
&EnvironmentOptions::allow_addons,
kAllowedInEnvvar);
kAllowedInEnvvar,
false,
OptionNamespaces::kPermissionNamespace);
AddOption("--allow-child-process",
"allow use of child process when any permissions are set",
&EnvironmentOptions::allow_child_process,
kAllowedInEnvvar);
kAllowedInEnvvar,
false,
OptionNamespaces::kPermissionNamespace);
AddOption("--allow-inspector",
"allow use of inspector when any permissions are set",
&EnvironmentOptions::allow_inspector,
kAllowedInEnvvar);
kAllowedInEnvvar,
false,
OptionNamespaces::kPermissionNamespace);
AddOption("--allow-net",
"allow use of network when any permissions are set",
&EnvironmentOptions::allow_net,
kAllowedInEnvvar);
kAllowedInEnvvar,
false,
OptionNamespaces::kPermissionNamespace);
AddOption("--allow-wasi",
"allow wasi when any permissions are set",
&EnvironmentOptions::allow_wasi,
kAllowedInEnvvar);
kAllowedInEnvvar,
false,
OptionNamespaces::kPermissionNamespace);
AddOption("--allow-worker",
"allow worker threads when any permissions are set",
&EnvironmentOptions::allow_worker_threads,
kAllowedInEnvvar);
kAllowedInEnvvar,
false,
OptionNamespaces::kPermissionNamespace);
AddOption("--experimental-repl-await",
"experimental await keyword support in REPL",
&EnvironmentOptions::experimental_repl_await,
Expand Down
3 changes: 2 additions & 1 deletion src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,8 @@ std::vector<std::string> MapAvailableNamespaces();
#define OPTION_NAMESPACE_LIST(V) \
V(kNoNamespace, "") \
V(kTestRunnerNamespace, "testRunner") \
V(kWatchNamespace, "watch")
V(kWatchNamespace, "watch") \
V(kPermissionNamespace, "permission")

enum class OptionNamespaces {
#define V(name, _) name,
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/permission/child-process-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const { spawnSync } = require('child_process');
spawnSync(process.execPath, ['--version']);
6 changes: 6 additions & 0 deletions test/fixtures/permission/config-addons-wasi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"permission": {
"allow-addons": true,
"allow-wasi": true
}
}
6 changes: 6 additions & 0 deletions test/fixtures/permission/config-child-worker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"permission": {
"allow-child-process": true,
"allow-worker": true
}
}
10 changes: 10 additions & 0 deletions test/fixtures/permission/config-fs-read-write.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"permission": {
"allow-fs-read": [
"*"
],
"allow-fs-write": [
"*"
]
}
}
6 changes: 6 additions & 0 deletions test/fixtures/permission/config-net-inspector.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"permission": {
"allow-net": true,
"allow-inspector": true
}
}
1 change: 1 addition & 0 deletions test/fixtures/permission/fs-read-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('fs').readFileSync(__filename);
6 changes: 6 additions & 0 deletions test/fixtures/permission/fs-write-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const fs = require('fs');
const path = require('path');
const os = require('os');

const tmpFile = path.join(os.tmpdir(), 'permission-test-' + Date.now() + '.txt');
fs.writeFileSync(tmpFile, 'test');
107 changes: 107 additions & 0 deletions test/parallel/test-permission-config-file.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { describe, it } from 'node:test';

describe('Permission model config file support', () => {
it('should load filesystem read/write permissions from config file', async () => {
const configPath = fixtures.path('permission/config-fs-read-write.json');
const readTestPath = fixtures.path('permission/fs-read-test.js');
const writeTestPath = fixtures.path('permission/fs-write-test.js');

{
const result = await spawnPromisified(process.execPath, [
'--permission',
'--experimental-config-file',
configPath,
readTestPath,
]);
assert.strictEqual(result.code, 0);
}

{
const result = await spawnPromisified(process.execPath, [
'--permission',
'--experimental-config-file',
configPath,
writeTestPath,
]);
assert.strictEqual(result.code, 0);
}
});

it('should load child process and worker permissions from config file', async () => {
const configPath = fixtures.path('permission/config-child-worker.json');
const childTestPath = fixtures.path('permission/child-process-test.js');

const result = await spawnPromisified(process.execPath, [
'--permission',
'--experimental-config-file',
configPath,
'--allow-fs-read=*',
childTestPath,
]);
assert.strictEqual(result.code, 0);
});

it('should load network and inspector permissions from config file', async () => {
const configPath = fixtures.path('permission/config-net-inspector.json');

const result = await spawnPromisified(process.execPath, [
'--permission',
'--experimental-config-file',
configPath,
'--allow-fs-read=*',
'-p',
'process.permission.has("net") && process.permission.has("inspector")',
]);
assert.match(result.stdout, /true/);
assert.strictEqual(result.code, 0);
});

it('should load addons and wasi permissions from config file', async () => {
const configPath = fixtures.path('permission/config-addons-wasi.json');

const result = await spawnPromisified(process.execPath, [
'--permission',
'--experimental-config-file',
configPath,
'--allow-fs-read=*',
'-p',
'process.permission.has("addon") && process.permission.has("wasi")',
]);
assert.match(result.stdout, /true/);
assert.strictEqual(result.code, 0);
});

it('should deny operations when permissions are not in config file', async () => {
const configPath = fixtures.path('permission/config-fs-read-write.json');

const result = await spawnPromisified(process.execPath, [
'--permission',
'--experimental-config-file',
configPath,
'--allow-fs-read=*',
'-p',
'process.permission.has("child")',
]);
assert.match(result.stdout, /false/);
assert.strictEqual(result.code, 0);
});

it('should combine config file permissions with CLI flags', async () => {
const configPath = fixtures.path('permission/config-fs-read-write.json');

const result = await spawnPromisified(process.execPath, [
'--permission',
'--experimental-config-file',
configPath,
'--allow-child-process',
'--allow-fs-read=*',
'-p',
'process.permission.has("child") && process.permission.has("fs.read")',
]);
assert.match(result.stdout, /true/);
assert.strictEqual(result.code, 0);
});
});
Loading