Skip to content

Commit 51938f0

Browse files
committed
src,loader,permission: throw on InternalWorker use
Previously this PR it was expected that InternalWorker usage doesn't require the --allow-worker when the permission model is enabled. This, however, exposes a vulnerability whenever the instance gets accessed by the user. For example through diagnostics_channel.subscribe('worker_threads') PR-URL: nodejs-private/node-private#629 Refs: https://hackerone.com/reports/2575105 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> CVE-ID: CVE-2025-23083
1 parent b9ce2bc commit 51938f0

5 files changed

+68
-8
lines changed

doc/api/cli.md

+2
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,8 @@ added:
10511051
10521052
Enable module mocking in the test runner.
10531053

1054+
This feature requires `--allow-worker` if used with the [Permission Model][].
1055+
10541056
### `--experimental-transform-types`
10551057

10561058
<!-- YAML

src/node_worker.cc

+2-4
Original file line numberDiff line numberDiff line change
@@ -493,11 +493,9 @@ Worker::~Worker() {
493493

494494
void Worker::New(const FunctionCallbackInfo<Value>& args) {
495495
Environment* env = Environment::GetCurrent(args);
496+
THROW_IF_INSUFFICIENT_PERMISSIONS(
497+
env, permission::PermissionScope::kWorkerThreads, "");
496498
bool is_internal = args[5]->IsTrue();
497-
if (!is_internal) {
498-
THROW_IF_INSUFFICIENT_PERMISSIONS(
499-
env, permission::PermissionScope::kWorkerThreads, "");
500-
}
501499
Isolate* isolate = args.GetIsolate();
502500

503501
CHECK(args.IsConstructCall());

test/es-module/test-esm-loader-hooks.mjs

+4-4
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ describe('Loader hooks', { concurrency: !process.env.TEST_PARALLEL }, () => {
179179
});
180180
});
181181

182-
it('should work without worker permission', async () => {
182+
it('should not work without worker permission', async () => {
183183
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
184184
'--no-warnings',
185185
'--permission',
@@ -190,9 +190,9 @@ describe('Loader hooks', { concurrency: !process.env.TEST_PARALLEL }, () => {
190190
fixtures.path('es-modules/esm-top-level-await.mjs'),
191191
]);
192192

193-
assert.strictEqual(stderr, '');
194-
assert.match(stdout, /^1\r?\n2\r?\n$/);
195-
assert.strictEqual(code, 0);
193+
assert.match(stderr, /Error: Access to this API has been restricted/);
194+
assert.strictEqual(stdout, '');
195+
assert.strictEqual(code, 1);
196196
assert.strictEqual(signal, null);
197197
});
198198

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Flags: --experimental-permission --allow-fs-read=* --experimental-test-module-mocks
2+
'use strict';
3+
4+
const common = require('../common');
5+
const assert = require('node:assert');
6+
7+
{
8+
const diagnostics_channel = require('node:diagnostics_channel');
9+
diagnostics_channel.subscribe('worker_threads', common.mustNotCall());
10+
const { mock } = require('node:test');
11+
12+
// Module mocking should throw instead of posting to worker_threads dc
13+
assert.throws(() => {
14+
mock.module('node:path');
15+
}, common.expectsError({
16+
code: 'ERR_ACCESS_DENIED',
17+
permission: 'WorkerThreads',
18+
}));
19+
}

test/parallel/test-runner-module-mocking.js

+41
Original file line numberDiff line numberDiff line change
@@ -650,3 +650,44 @@ test('wrong import syntax should throw error after module mocking', async () =>
650650
assert.match(stderr, /Error \[ERR_MODULE_NOT_FOUND\]: Cannot find module/);
651651
assert.strictEqual(code, 1);
652652
});
653+
654+
test('should throw ERR_ACCESS_DENIED when permission model is enabled', async (t) => {
655+
const cwd = fixtures.path('test-runner');
656+
const fixture = fixtures.path('test-runner', 'mock-nm.js');
657+
const args = [
658+
'--experimental-permission',
659+
'--allow-fs-read=*',
660+
'--experimental-test-module-mocks',
661+
fixture,
662+
];
663+
const {
664+
code,
665+
stdout,
666+
} = await common.spawnPromisified(process.execPath, args, { cwd });
667+
668+
assert.strictEqual(code, 1);
669+
assert.match(stdout, /Error: Access to this API has been restricted/);
670+
assert.match(stdout, /permission: 'WorkerThreads'/);
671+
});
672+
673+
test('should work when --allow-worker is passed and permission model is enabled', async (t) => {
674+
const cwd = fixtures.path('test-runner');
675+
const fixture = fixtures.path('test-runner', 'mock-nm.js');
676+
const args = [
677+
'--experimental-permission',
678+
'--allow-fs-read=*',
679+
'--allow-worker',
680+
'--experimental-test-module-mocks',
681+
fixture,
682+
];
683+
const {
684+
code,
685+
stdout,
686+
stderr,
687+
signal,
688+
} = await common.spawnPromisified(process.execPath, args, { cwd });
689+
690+
assert.strictEqual(code, 0, stderr);
691+
assert.strictEqual(signal, null);
692+
assert.match(stdout, /pass 1/, stderr);
693+
});

0 commit comments

Comments
 (0)