Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions apps/desktop/src/main/command-runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,45 @@ describe('rejectWindowsSandboxedNodeTestIsolation node version instruction', ()
}
});

// Issue #734: a release newer than the last one known to ship libuv < 1.53 may carry the
// upstream pipe fix, so the in-sandbox guard (which reads the real libuv) decides instead.
it('keeps rejecting every release known to ship libuv older than 1.53', () => {
vi.spyOn(secureLogger, 'warn').mockImplementation(() => undefined);
for (const nodeVersion of [
{ major: 22, minor: 23, build: 3 },
{ major: 24, minor: 21, build: 0 },
{ major: 26, minor: 10, build: 0 },
{ major: 23, minor: 11, build: 1 },
{ major: 25, minor: 9, build: 0 },
]) {
expect(() =>
rejectWindowsSandboxedNodeTestIsolation(nodeExe, ['--test'], {
...sandboxedWindows,
nodeVersion,
}),
).toThrow(CommandRunnerError);
}
});

it('leaves releases newer than the known old-libuv ones to the in-sandbox guard', () => {
const warn = vi.spyOn(secureLogger, 'warn').mockImplementation(() => undefined);
for (const nodeVersion of [
{ major: 22, minor: 23, build: 4 },
{ major: 22, minor: 24, build: 0 },
{ major: 24, minor: 21, build: 1 },
{ major: 26, minor: 11, build: 0 },
{ major: 27, minor: 0, build: 0 },
]) {
expect(() =>
rejectWindowsSandboxedNodeTestIsolation(nodeExe, ['--test'], {
...sandboxedWindows,
nodeVersion,
}),
).not.toThrow();
}
expect(warn).not.toHaveBeenCalled();
});

it('never reads a version or logs when the check does not reject the command', () => {
const warn = vi.spyOn(secureLogger, 'warn').mockImplementation(() => undefined);
expect(
Expand Down
26 changes: 23 additions & 3 deletions apps/desktop/src/main/command-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,25 @@ const NODE_TEST_ISOLATION_OWN_SCRIPT_NOTE =
'If --test is an argument to your own script, the same placement also passes, and Node ' +
'ignores the flag without --test.';

// The newest release of each line known to bundle libuv older than 1.53 (checked against
// nodejs.org/dist/index.json on 2026-10-06). libuv 1.53.0 fixes the AppContainer pipe name
// (libuv/libuv#5181). A newer release is not rejected here: the in-sandbox pipe guard reads its
// real libuv and still fails fast when the fix is missing, so this table never needs an update
// to stay safe (Issue #734). Lines missing from it are older and all bundle an older libuv,
// except the ones after it, which are left to the guard.
const NODE_LAST_RELEASE_WITH_OLD_LIBUV: ReadonlyMap<number, readonly [number, number]> = new Map([
[22, [23, 3]],
[24, [21, 0]],
[26, [10, 0]],
]);
const NODE_NEWEST_LINE_WITH_OLD_LIBUV = 26;

function mayCarryLibuvPipeFix(version: WindowsExecutableFileVersion): boolean {
const last = NODE_LAST_RELEASE_WITH_OLD_LIBUV.get(version.major);
if (last === undefined) return version.major > NODE_NEWEST_LINE_WITH_OLD_LIBUV;
return compareMajorMinor([version.minor, version.build], last) > 0;
}

function formatWindowsExecutableFileVersion(version: WindowsExecutableFileVersion): string {
return `v${version.major}.${version.minor}.${version.build}`;
}
Expand Down Expand Up @@ -581,13 +600,14 @@ export function rejectWindowsSandboxedNodeTestIsolation(
options: {
platform?: NodeJS.Platform;
sandboxed: boolean;
// The rejected node.exe's own file version, when the caller could read it (Issue #549). Only
// used to pick the one instruction that matches this Node; never used to decide whether to
// reject.
// The rejected node.exe's own file version, when the caller could read it (Issue #549). Picks
// the one instruction that matches this Node, and lets a release that may carry the libuv
// fix through (Issue #734).
nodeVersion?: WindowsExecutableFileVersion | null;
},
): void {
if (!shouldRejectWindowsSandboxedNodeTestIsolation(canonicalExecutable, argv, options)) return;
if (options.nodeVersion && mayCarryLibuvPipeFix(options.nodeVersion)) return;
secureLogger.warn(
'Node test process isolation was rejected before approval',
{
Expand Down
Loading