src,permission: add --allow-inspector ability

Refs: https://github.com/nodejs/node/issues/48534
PR-URL: https://github.com/nodejs/node/pull/59711
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
This commit is contained in:
Rafael Gonzaga 2025-09-11 17:10:02 -03:00 committed by GitHub
parent ac131bdc01
commit 29738c7b42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 112 additions and 5 deletions

View File

@ -275,6 +275,36 @@ When passing a single flag with a comma a warning will be displayed.
Examples can be found in the [File System Permissions][] documentation. Examples can be found in the [File System Permissions][] documentation.
### `--allow-inspector`
<!-- YAML
added: REPLACEME
-->
> Stability: 1.0 - Early development
When using the [Permission Model][], the process will not be able to connect
through inspector protocol.
Attempts to do so will throw an `ERR_ACCESS_DENIED` unless the
user explicitly passes the `--allow-inspector` flag when starting Node.js.
Example:
```js
const { Session } = require('node:inspector/promises');
const session = new Session();
session.connect();
```
```console
$ node --permission index.js
Error: connect ERR_ACCESS_DENIED Access to this API has been restricted. Use --allow-inspector to manage permissions.
code: 'ERR_ACCESS_DENIED',
}
```
### `--allow-net` ### `--allow-net`
<!-- YAML <!-- YAML
@ -3427,6 +3457,7 @@ one is included in the list below.
* `--allow-child-process` * `--allow-child-process`
* `--allow-fs-read` * `--allow-fs-read`
* `--allow-fs-write` * `--allow-fs-write`
* `--allow-inspector`
* `--allow-net` * `--allow-net`
* `--allow-wasi` * `--allow-wasi`
* `--allow-worker` * `--allow-worker`

View File

@ -51,7 +51,8 @@ flag.
When starting Node.js with `--permission`, When starting Node.js with `--permission`,
the ability to access the file system through the `fs` module, access the network, the ability to access the file system through the `fs` module, access the network,
spawn processes, use `node:worker_threads`, use native addons, use WASI, and spawn processes, use `node:worker_threads`, use native addons, use WASI, and
enable the runtime inspector will be restricted. enable the runtime inspector will be restricted (the listener for SIGUSR1 won't
be created).
```console ```console
$ node --permission index.js $ node --permission index.js

View File

@ -45,6 +45,9 @@
} }
] ]
}, },
"allow-inspector": {
"type": "boolean"
},
"allow-net": { "allow-net": {
"type": "boolean" "type": "boolean"
}, },

View File

@ -85,6 +85,9 @@ Allow using native addons when using the permission model.
.It Fl -allow-child-process .It Fl -allow-child-process
Allow spawning process when using the permission model. Allow spawning process when using the permission model.
. .
.It Fl -allow-inspector
Allow inspector access when using the permission model.
.
.It Fl -allow-net .It Fl -allow-net
Allow network access when using the permission model. Allow network access when using the permission model.
. .

View File

@ -40,6 +40,7 @@ module.exports = ObjectFreeze({
'--allow-addons', '--allow-addons',
'--allow-child-process', '--allow-child-process',
'--allow-net', '--allow-net',
'--allow-inspector',
'--allow-wasi', '--allow-wasi',
'--allow-worker', '--allow-worker',
]; ];

View File

@ -580,6 +580,7 @@ function initializePermission() {
const warnFlags = [ const warnFlags = [
'--allow-addons', '--allow-addons',
'--allow-child-process', '--allow-child-process',
'--allow-inspector',
'--allow-wasi', '--allow-wasi',
'--allow-worker', '--allow-worker',
]; ];

View File

@ -912,8 +912,10 @@ Environment::Environment(IsolateData* isolate_data,
options_->allow_native_addons = false; options_->allow_native_addons = false;
permission()->Apply(this, {"*"}, permission::PermissionScope::kAddon); permission()->Apply(this, {"*"}, permission::PermissionScope::kAddon);
} }
flags_ = flags_ | EnvironmentFlags::kNoCreateInspector; if (!options_->allow_inspector) {
permission()->Apply(this, {"*"}, permission::PermissionScope::kInspector); flags_ = flags_ | EnvironmentFlags::kNoCreateInspector;
permission()->Apply(this, {"*"}, permission::PermissionScope::kInspector);
}
if (!options_->allow_child_process) { if (!options_->allow_child_process) {
permission()->Apply( permission()->Apply(
this, {"*"}, permission::PermissionScope::kChildProcess); this, {"*"}, permission::PermissionScope::kChildProcess);

View File

@ -606,6 +606,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"allow use of child process when any permissions are set", "allow use of child process when any permissions are set",
&EnvironmentOptions::allow_child_process, &EnvironmentOptions::allow_child_process,
kAllowedInEnvvar); kAllowedInEnvvar);
AddOption("--allow-inspector",
"allow use of inspector when any permissions are set",
&EnvironmentOptions::allow_inspector,
kAllowedInEnvvar);
AddOption("--allow-net", AddOption("--allow-net",
"allow use of network when any permissions are set", "allow use of network when any permissions are set",
&EnvironmentOptions::allow_net, &EnvironmentOptions::allow_net,

View File

@ -141,6 +141,7 @@ class EnvironmentOptions : public Options {
std::vector<std::string> allow_fs_read; std::vector<std::string> allow_fs_read;
std::vector<std::string> allow_fs_write; std::vector<std::string> allow_fs_write;
bool allow_addons = false; bool allow_addons = false;
bool allow_inspector = false;
bool allow_child_process = false; bool allow_child_process = false;
bool allow_net = false; bool allow_net = false;
bool allow_wasi = false; bool allow_wasi = false;

View File

@ -27,7 +27,8 @@ namespace permission {
#define WORKER_THREADS_PERMISSIONS(V) \ #define WORKER_THREADS_PERMISSIONS(V) \
V(WorkerThreads, "worker", PermissionsRoot, "--allow-worker") V(WorkerThreads, "worker", PermissionsRoot, "--allow-worker")
#define INSPECTOR_PERMISSIONS(V) V(Inspector, "inspector", PermissionsRoot, "") #define INSPECTOR_PERMISSIONS(V) \
V(Inspector, "inspector", PermissionsRoot, "--allow-inspector")
#define NET_PERMISSIONS(V) V(Net, "net", PermissionsRoot, "--allow-net") #define NET_PERMISSIONS(V) V(Net, "net", PermissionsRoot, "--allow-net")

View File

@ -364,6 +364,9 @@ if (hasCrypto) {
knownGlobals.add(globalThis.SubtleCrypto); knownGlobals.add(globalThis.SubtleCrypto);
} }
const { Worker } = require('node:worker_threads');
knownGlobals.add(Worker);
function allowGlobals(...allowlist) { function allowGlobals(...allowlist) {
for (const val of allowlist) { for (const val of allowlist) {
knownGlobals.add(val); knownGlobals.add(val);

View File

@ -0,0 +1,55 @@
// Flags: --permission --allow-fs-read=* --allow-inspector
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const { Session } = require('node:inspector/promises');
const assert = require('node:assert');
const session = new Session();
session.connect();
// Guarantee WorkerImpl doesn't gets bypassed
(async () => {
await session.post('Debugger.enable');
await session.post('Runtime.enable');
globalThis.Worker = require('node:worker_threads').Worker;
const { result: { objectId } } = await session.post('Runtime.evaluate', { expression: 'Worker' });
const { internalProperties } = await session.post('Runtime.getProperties', { objectId: objectId });
const {
value: {
value: { scriptId }
}
} = internalProperties.filter((prop) => prop.name === '[[FunctionLocation]]')[0];
const { scriptSource } = await session.post('Debugger.getScriptSource', { scriptId });
const lineNumber = scriptSource.substring(0, scriptSource.indexOf('new WorkerImpl')).split('\n').length;
// Attempt to bypass it based on https://hackerone.com/reports/1962701
await session.post('Debugger.setBreakpointByUrl', {
lineNumber: lineNumber,
url: 'node:internal/worker',
columnNumber: 0,
condition: '((isInternal = true),false)'
});
assert.throws(() => {
// eslint-disable-next-line no-undef
new Worker(`
const child_process = require("node:child_process");
console.log(child_process.execSync("ls -l").toString());
console.log(require("fs").readFileSync("/etc/passwd").toString())
`, {
eval: true,
});
}, {
message: 'Access to this API has been restricted. Use --allow-worker to manage permissions.',
code: 'ERR_ACCESS_DENIED',
permission: 'WorkerThreads'
});
})().then(common.mustCall());

View File

@ -22,7 +22,7 @@ if (!common.hasCrypto)
const session = new Session(); const session = new Session();
session.connect(); session.connect();
}, common.expectsError({ }, common.expectsError({
message: 'Access to this API has been restricted. ', message: 'Access to this API has been restricted. Use --allow-inspector to manage permissions.',
code: 'ERR_ACCESS_DENIED', code: 'ERR_ACCESS_DENIED',
permission: 'Inspector', permission: 'Inspector',
})); }));

View File

@ -7,6 +7,7 @@ const assert = require('assert');
const warnFlags = [ const warnFlags = [
'--allow-addons', '--allow-addons',
'--allow-child-process', '--allow-child-process',
'--allow-inspector',
'--allow-wasi', '--allow-wasi',
'--allow-worker', '--allow-worker',
]; ];