From fbe4f64f3ada04892a3f021d4876b6c448179a8a Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Wed, 5 Aug 2026 10:51:57 -0400 Subject: [PATCH] worker: don't leak watch messages to listeners Under `node --watch`, each worker reports its module dependencies to the watch supervisor over the worker's message channel. The main thread forwarded those `watch:require` and `watch:import` messages to the supervisor but then still emitted them to user `worker.on('message')` listeners, so applications received undocumented internal messages. Consume the forwarded watch dependency messages instead of surfacing them to user listeners. Fixes: https://github.com/nodejs/node/issues/65044 Signed-off-by: Paul Bouchon --- lib/internal/worker.js | 9 ++++++ test/sequential/test-watch-mode-worker.mjs | 32 ++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib/internal/worker.js b/lib/internal/worker.js index 3afd2180d705..8de90b1301b5 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -347,11 +347,20 @@ class Worker extends EventEmitter { ) { const { isMainThread } = internalBinding('worker'); if (isMainThread) { + let isWatchDependencyMessage = false; if (ArrayIsArray(message?.['watch:require'])) { process.send({ 'watch:require': message['watch:require'] }); + isWatchDependencyMessage = true; } if (ArrayIsArray(message?.['watch:import'])) { process.send({ 'watch:import': message['watch:import'] }); + isWatchDependencyMessage = true; + } + if (isWatchDependencyMessage) { + // These are internal watch mode dependency reports that have been + // forwarded to the watch supervisor above. They must not be + // surfaced to user 'message' listeners on the Worker. + return; } } } diff --git a/test/sequential/test-watch-mode-worker.mjs b/test/sequential/test-watch-mode-worker.mjs index 330ecbeddfc4..d0c84a5e9070 100644 --- a/test/sequential/test-watch-mode-worker.mjs +++ b/test/sequential/test-watch-mode-worker.mjs @@ -299,4 +299,36 @@ new Worker(new URL(${JSON.stringify(pathToFileURL(worker))})); `Completed running ${inspect(file)}. Waiting for file changes before restarting...`, ]); }); + + it('should not surface internal watch messages to worker message listeners', async () => { + // Regression test for https://github.com/nodejs/node/issues/65044: + // watch mode reports each worker's dependencies to the supervisor over the + // worker's message channel. Those internal `watch:require`/`watch:import` + // messages must not be delivered to user `worker.on('message')` listeners. + const dir = createTmpDir(); + const worker = path.join(dir, 'worker.js'); + + writeFileSync(worker, ` +console.log('worker running'); +`); + + const file = createTmpFile(` +const { Worker } = require('node:worker_threads'); +const w = new Worker(${JSON.stringify(worker)}); +w.on('message', (message) => { + console.log('LEAKED ' + JSON.stringify(message)); +}); +`, '.js', dir); + + const { stderr, stdout } = await runWriteSucceed({ + file, + watchedFile: worker, + }); + + assert.strictEqual(stderr, ''); + assert.ok( + !stdout.some((line) => line.startsWith('LEAKED') || line.includes('watch:require')), + `internal watch message leaked to a user listener: ${inspect(stdout)}`, + ); + }); });