Skip to content

Commit 6729b80

Browse files
committed
doc: clarify that process._debugProcess() is not restricted by the Permission Model
1 parent cdc5774 commit 6729b80

8 files changed

Lines changed: 131 additions & 22 deletions

File tree

SECURITY.md

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -152,28 +152,33 @@ does not trust is considered a vulnerability:
152152
the correct use of Node.js APIs.
153153
* The unavailability of the runtime, including the unbounded degradation of its
154154
performance.
155-
* Memory leaks qualify as vulnerabilities when all of the following criteria are met:
156-
* The API is being correctly used.
157-
* The API doesn't have a warning against its usage in a production environment.
158-
* The API is public and documented.
159-
* The API is on stable (2.0) status.
160-
* The memory leak is significant enough to cause a denial of service quickly
161-
or in a context not controlled by the user (for example, HTTP parsing).
162-
* The memory leak is directly exploitable by an untrusted source without requiring application mistakes.
163-
* The leak cannot be reasonably mitigated through standard operational practices (like process recycling).
164-
* The leak occurs deterministically under normal usage patterns rather than edge cases.
165-
* The leak occurs at a rate that would cause practical resource exhaustion within a practical timeframe under
166-
typical workloads.
167-
* The attack demonstrates [asymmetric resource consumption](https://cwe.mitre.org/data/definitions/405.html),
168-
where the attacker expends significantly fewer resources than what's required by the server to process the
169-
attack. Attacks requiring comparable resources on the attacker's side (which can be mitigated through common
170-
practices like rate limiting) may not qualify.
171155

172156
If Node.js loads configuration files or runs code by default (without a
173157
specific request from the user), and this is not documented, it is considered a
174158
vulnerability.
175159
Vulnerabilities related to this case may be fixed by a documentation update.
176160

161+
#### Denial of Service (DoS) vulnerabilities
162+
163+
For a behavior to be considered a DoS vulnerability, the PoC must meet the following criteria:
164+
165+
* The API is being correctly used.
166+
* The API doesn't have a warning against its usage in a production environment.
167+
* The API is public and documented. If the API comes from JavaScript, the behavior must be
168+
well-defined in the [ECMAScript specification](https://tc39.es/ecma262/).
169+
* The API has stable (2.0) status.
170+
* The behavior is significant enough to cause a denial of service quickly
171+
or in a context not controlled by the Node.js application developer (for example, HTTP parsing).
172+
* The behavior is directly exploitable by an untrusted source without requiring application mistakes.
173+
* The behavior cannot be reasonably mitigated through standard operational practices (like process recycling).
174+
* The behavior occurs deterministically under normal usage patterns rather than edge cases.
175+
* The behavior occurs at a rate that would cause practical resource exhaustion within a practical timeframe under
176+
typical workloads.
177+
* The attack demonstrates [asymmetric resource consumption](https://cwe.mitre.org/data/definitions/405.html),
178+
where the attacker expends significantly fewer resources than what's required by the server to process the
179+
attack. Attacks requiring comparable resources on the attacker's side (which can be mitigated through common
180+
practices like rate limiting) may not qualify.
181+
177182
**Node.js does NOT trust**:
178183

179184
* Data received from the remote end of inbound network connections

doc/api/deprecations.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4527,16 +4527,27 @@ deprecated and will throw an error in a future version.
45274527
45284528
<!-- YAML
45294529
changes:
4530+
- version: REPLACEME
4531+
pr-url: https://github.com/nodejs/node/pull/62401
4532+
description: Runtime deprecation.
45304533
- version: REPLACEME
45314534
pr-url: https://github.com/nodejs/node/pull/62395
45324535
description: Documentation-only deprecation.
45334536
-->
45344537
4535-
Type: Documentation-only
4538+
Type: Runtime
45364539
45374540
[`module.register()`][] is deprecated. Use [`module.registerHooks()`][]
45384541
instead.
45394542
4543+
The `module.register()` API provides off-thread async hooks for customizing ES modules;
4544+
the `module.registerHooks()` API provides similar hooks that are synchronous, in-thread, and
4545+
work for all types of modules.
4546+
Supporting async hooks has proven to be complex, involving worker threads orchestration, and there are issues
4547+
that have proven unresolveable. See [caveats of asynchronous customization hooks][]. Please migrate to
4548+
`module.registerHooks()` as soon as possible as `module.register()` will be
4549+
removed in a future version of Node.js.
4550+
45404551
[DEP0142]: #dep0142-repl_builtinlibs
45414552
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
45424553
[RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3
@@ -4696,6 +4707,7 @@ instead.
46964707
[`zlib.bytesWritten`]: zlib.md#zlibbyteswritten
46974708
[alloc]: buffer.md#static-method-bufferallocsize-fill-encoding
46984709
[alloc_unsafe_size]: buffer.md#static-method-bufferallocunsafesize
4710+
[caveats of asynchronous customization hooks]: module.md#caveats-of-asynchronous-customization-hooks
46994711
[from_arraybuffer]: buffer.md#static-method-bufferfromarraybuffer-byteoffset-length
47004712
[from_string_encoding]: buffer.md#static-method-bufferfromstring-encoding
47014713
[legacy URL API]: url.md#legacy-url-api

doc/api/module.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ added:
180180
- v18.19.0
181181
deprecated: REPLACEME
182182
changes:
183+
- version: REPLACEME
184+
pr-url: https://github.com/nodejs/node/pull/62401
185+
description: Runtime deprecation (DEP0205).
183186
- version: REPLACEME
184187
pr-url: https://github.com/nodejs/node/pull/62395
185188
description: Documentation-only deprecation (DEP0205). Use

doc/api/permissions.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,20 @@ There are constraints you need to know before using this system:
244244
* Using existing file descriptors via the `node:fs` module bypasses the
245245
Permission Model.
246246

247+
#### process._debugProcess() and cross-process Inspector activation
248+
249+
The kInspector permission scope restricts the current process from opening its own V8 Inspector. However, process._debugProcess(pid) — which sends an OS-level signal (SIGUSR1 on POSIX, a remote thread on Windows) to an external process — is not gated by the kInspector scope or any other Permission Model scope.
250+
251+
A sandboxed process running under --permission with no additional grants can call process._debugProcess(pid) to force another Node.js process to open its V8 Inspector. The target process does not need to be running under --permission for this to work — any Node.js process running on the same host under the same OS user can be signaled.
252+
253+
This is consistent with the Node.js threat model: Node.js trusts the OS environment in which it runs. Cross-process signaling is an operating-system-level capability; restricting it is the responsibility of the operator (for example, using OS-level process isolation, separate OS users per process, or seccomp/AppArmor profiles on Linux).
254+
255+
Developers relying on --permission to sandbox untrusted code should be aware that:
256+
257+
* process._debugProcess() is callable from any sandboxed process with no grants.
258+
* If a target Node.js process is running on the same host under the same OS user, it can be forced to open its Inspector via this API.
259+
* To prevent this, run sandboxed and target processes under different OS users, or use OS-level isolation mechanisms outside of Node.js.
260+
247261
#### Limitations and Known Issues
248262

249263
* Symbolic links will be followed even to locations outside of the set of paths

doc/api/test.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3913,7 +3913,9 @@ added: v25.0.0
39133913

39143914
* Type: {number}
39153915

3916-
Number of times the test has been attempted.
3916+
The attempt number of the test. This value is zero-based, so the first attempt is `0`,
3917+
the second attempt is `1`, and so on. This property is useful in conjunction with the
3918+
`--test-rerun-failures` option to determine which attempt the test is currently running.
39173919

39183920
### `context.workerId`
39193921

@@ -4286,9 +4288,9 @@ added: REPLACEME
42864288

42874289
* Type: {number}
42884290

4289-
The current attempt number of the suite. Used in conjunction with the
4290-
`--test-rerun-failures` option to determine the attempt number of the current
4291-
run.
4291+
The attempt number of the suite. This value is zero-based, so the first attempt is `0`,
4292+
the second attempt is `1`, and so on. This property is useful in conjunction with the
4293+
`--test-rerun-failures` option to determine the attempt number of the current run.
42924294

42934295
### `context.diagnostic(message)`
42944296

lib/internal/modules/esm/loader.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ const {
3131
} = require('internal/errors').codes;
3232
const { getOptionValue } = require('internal/options');
3333
const { isURL, pathToFileURL } = require('internal/url');
34-
const { kEmptyObject } = require('internal/util');
34+
const {
35+
getDeprecationWarningEmitter,
36+
kEmptyObject,
37+
} = require('internal/util');
3538
const {
3639
compileSourceTextModule,
3740
SourceTextModuleTypes: { kUser },
@@ -955,7 +958,16 @@ function isCascadedLoaderInitialized() {
955958
* });
956959
* ```
957960
*/
961+
const emitRegisterDeprecation = getDeprecationWarningEmitter(
962+
'DEP0205',
963+
'`module.register()` is deprecated. Use `module.registerHooks()` instead.',
964+
undefined,
965+
false,
966+
);
967+
958968
function register(specifier, parentURL = undefined, options) {
969+
emitRegisterDeprecation();
970+
959971
if (parentURL != null && typeof parentURL === 'object' && !isURL(parentURL)) {
960972
options = parentURL;
961973
parentURL = options.parentURL;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { spawnPromisified } from '../common/index.mjs';
2+
import * as fixtures from '../common/fixtures.mjs';
3+
4+
import assert from 'node:assert';
5+
import { execPath } from 'node:process';
6+
import { describe, it } from 'node:test';
7+
8+
const urlToRegister = fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs');
9+
const urlToRegisterEscaped = JSON.stringify(urlToRegister.href);
10+
11+
12+
describe('module.register() deprecation (DEP0205)', { concurrency: !process.env.TEST_PARALLEL }, () => {
13+
it('emits DEP0205 when module.register() is called', async () => {
14+
const { code, stderr } = await spawnPromisified(execPath, [
15+
'--input-type=module',
16+
'--eval',
17+
`import { register } from 'node:module'; register(${urlToRegisterEscaped});`,
18+
]);
19+
20+
assert.match(stderr, /\[DEP0205\]/);
21+
assert.strictEqual(code, 0);
22+
});
23+
24+
it('only emits the warning once per process', async () => {
25+
const { code, stderr } = await spawnPromisified(execPath, [
26+
'--input-type=module',
27+
'--eval',
28+
`import { register } from 'node:module';
29+
register(${urlToRegisterEscaped});
30+
register(${urlToRegisterEscaped});`,
31+
]);
32+
33+
assert.strictEqual(stderr.split('[DEP0205]').length - 1, 1);
34+
assert.strictEqual(code, 0);
35+
});
36+
37+
it('does not emit when --no-deprecation is set', async () => {
38+
const { code, stderr } = await spawnPromisified(execPath, [
39+
'--no-deprecation',
40+
'--input-type=module',
41+
'--eval',
42+
`import { register } from 'node:module'; register(${urlToRegisterEscaped});`,
43+
]);
44+
45+
assert.doesNotMatch(stderr, /DEP0205/);
46+
assert.strictEqual(code, 0);
47+
});
48+
49+
it('throws when --throw-deprecation is set', async () => {
50+
const { code, stderr } = await spawnPromisified(execPath, [
51+
'--throw-deprecation',
52+
'--input-type=module',
53+
'--eval',
54+
`import { register } from 'node:module'; register(${urlToRegisterEscaped});`,
55+
]);
56+
57+
assert.match(stderr, /DEP0205/);
58+
assert.notStrictEqual(code, 0);
59+
});
60+
});

test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { spawnSyncAndAssert } from '../common/child_process.js';
77
spawnSyncAndAssert(
88
execPath,
99
[
10+
'--no-deprecation',
1011
'--no-experimental-require-module',
1112
'--import',
1213
fixtures.fileURL('es-module-loaders/builtin-named-exports.mjs'),

0 commit comments

Comments
 (0)