From 7c3933ce175bb88a0246d069e78447762b6f9d8a Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Thu, 17 Sep 2026 07:29:38 -0700 Subject: [PATCH] fix(runtime): tolerate denied Node probe spawns (#150858) --- node-runtime-recovery.mjs | 32 ++++++++++++------------- src/infra/node-runtime-recovery.test.ts | 14 +++++++++++ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/node-runtime-recovery.mjs b/node-runtime-recovery.mjs index 27d9a27889cc..e6ec2ac9c3a5 100644 --- a/node-runtime-recovery.mjs +++ b/node-runtime-recovery.mjs @@ -393,23 +393,23 @@ export function isUsableNode(nodePath, { allowCwd = false, trustedRoot, env = pr probeEnv[key] = value; } } - const result = spawnSync( - resolved, - [ - "-e", - `const probe = ${SQLITE_CAPABILITY_PROBE}; process.stdout.write(JSON.stringify({ version: process.versions.node, probe }));`, - ], - { - encoding: "utf8", - env: probeEnv, - timeout: 5_000, - killSignal: "SIGKILL", - maxBuffer: 65_536, - windowsHide: true, - stdio: ["ignore", "pipe", "pipe"], - }, - ); try { + const result = spawnSync( + resolved, + [ + "-e", + `const probe = ${SQLITE_CAPABILITY_PROBE}; process.stdout.write(JSON.stringify({ version: process.versions.node, probe }));`, + ], + { + encoding: "utf8", + env: probeEnv, + timeout: 5_000, + killSignal: "SIGKILL", + maxBuffer: 65_536, + windowsHide: true, + stdio: ["ignore", "pipe", "pipe"], + }, + ); const details = JSON.parse(result.stdout); return result.status === 0 && !nodeRuntimeFailure(details.version, details.probe); } catch { diff --git a/src/infra/node-runtime-recovery.test.ts b/src/infra/node-runtime-recovery.test.ts index aa6962f0e80d..0770cd51f89d 100644 --- a/src/infra/node-runtime-recovery.test.ts +++ b/src/infra/node-runtime-recovery.test.ts @@ -940,4 +940,18 @@ describe("candidate admission probe", () => { expect(isUsableNode(candidate)).toBe(false); }); }); + + it("rejects candidates when the permission model denies child processes", async () => { + await withRecoveryHome(async (home) => { + const candidate = await writeFixture(path.join(home, "bin/node")); + mocks.admissible.add(candidate); + mocks.probe.mockImplementation(() => { + throw Object.assign(new Error("Access to this API has been restricted"), { + code: "ERR_ACCESS_DENIED", + }); + }); + + expect(isUsableNode(candidate)).toBe(false); + }); + }); });