From c0fb04c8d21ab67ca06e6df283ee120166ed22e1 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 15 Sep 2026 01:25:41 -0700 Subject: [PATCH] fix(relay): open the real null device when detaching Windows stdio (#20808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(relay): open the real null device when detaching Windows stdio `openSync('NUL')` does not reach the null device on Windows. node's fs runs the path through `toNamespacedPath`, which resolves it against cwd and prefixes `\\?\` — and that prefix turns off DOS device-name mapping, so CreateFileW creates a regular file named `NUL` in the relay's install dir and pins fds 0/1 to it instead of to a discard sink. Verified on a Windows 11 host: `fs.openSync('NUL', 'w')` + a 5-byte write produced a 5-byte file named `NUL` in cwd. `\\.\NUL` is passed through `toNamespacedPath` verbatim; the same write discards and a read answers EOF, with no file created. It also escaped into shipped artifacts. release-cut.yml runs the relay watcher fault harness with cwd = out/relay/win32-x64, so every Windows installer since v1.4.169 carries `resources/relay/win32-x64/NUL`, which NSIS extracts as `_NUL`. * test(relay): prove the `\\?\` rewrite on a drive-letter path `toNamespacedPath('NUL')` off Windows only resolves against a POSIX cwd and stops; with no drive letter it never reaches the branch that adds `\\?\`. So the assertion held for the wrong reason and did not demonstrate the rewrite the comment describes. Assert it on an absolute drive path, which takes the same branch on every host. --- src/relay/relay-primary-channel.test.ts | 29 +++++++++++++++++++++++++ src/relay/relay-primary-channel.ts | 16 +++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 src/relay/relay-primary-channel.test.ts diff --git a/src/relay/relay-primary-channel.test.ts b/src/relay/relay-primary-channel.test.ts new file mode 100644 index 00000000000..d1ac209e161 --- /dev/null +++ b/src/relay/relay-primary-channel.test.ts @@ -0,0 +1,29 @@ +import { win32 } from 'node:path' +import { describe, expect, it } from 'vitest' +import { nullDevicePath } from './relay-primary-channel' + +describe('nullDevicePath', () => { + it('names the POSIX null device off win32', () => { + expect(nullDevicePath('linux')).toBe('/dev/null') + expect(nullDevicePath('darwin')).toBe('/dev/null') + }) + + /** + * The defect this pins: `openSync('NUL')` on Windows does NOT open the null device. + * node runs the path through `toNamespacedPath`, which resolves it against cwd and + * prefixes `\\?\` — and `\\?\` turns off DOS device-name mapping, so CreateFileW makes + * a real file. v1.4.203's Windows installer shipped one at + * `resources/relay/win32-x64/NUL` because of it. + */ + it('uses a device path win32 cannot rewrite into a file in the relay cwd', () => { + const path = nullDevicePath('win32') + + expect(path).toBe('\\\\.\\NUL') + expect(win32.toNamespacedPath(path)).toBe(path) + // Bare `NUL` never survives as a device name: it is resolved against cwd, and a + // drive-letter cwd then also takes the `\\?\` prefix. Spelled absolute because off + // Windows `resolve` finds no drive letter and stops before that second rewrite. + expect(win32.toNamespacedPath('NUL')).not.toBe('NUL') + expect(win32.toNamespacedPath(String.raw`C:\relay\NUL`)).toBe(String.raw`\\?\C:\relay\NUL`) + }) +}) diff --git a/src/relay/relay-primary-channel.ts b/src/relay/relay-primary-channel.ts index 9b2e50eaaa1..e3dbffeae7f 100644 --- a/src/relay/relay-primary-channel.ts +++ b/src/relay/relay-primary-channel.ts @@ -2,6 +2,17 @@ import { closeSync, openSync } from 'node:fs' import { RelayDispatcher } from './dispatcher' import { RELAY_SENTINEL } from './protocol' +/** + * Why the `\\.\` device prefix and not bare `NUL`: node's fs resolves a relative path + * through `toNamespacedPath`, which hands CreateFileW a `\\?\C:\…\NUL` — and that prefix + * disables DOS device-name mapping, so the open creates a real FILE named `NUL` in the + * relay's cwd and pins fds 0/1 to it. One shipped in the 1.4.203 Windows installer as + * `resources/relay/win32-x64/NUL`. A `\\.\` path is passed through verbatim. + */ +export function nullDevicePath(platform: NodeJS.Platform = process.platform): string { + return platform === 'win32' ? String.raw`\\.\NUL` : '/dev/null' +} + export class RelayPrimaryChannel { readonly dispatcher: RelayDispatcher private stdoutAlive = true @@ -111,14 +122,13 @@ export class RelayPrimaryChannel { // Already closed by the peer. } } - const devNull = process.platform === 'win32' ? 'NUL' : '/dev/null' try { - openSync(devNull, 'r') + openSync(nullDevicePath(), 'r') } catch { // Best-effort pin of the lowest free descriptor. } try { - openSync(devNull, 'w') + openSync(nullDevicePath(), 'w') } catch { // Best-effort pin of the next free descriptor. }