From 65d07a490f9a8cf5ce8a3bbc2936285182600450 Mon Sep 17 00:00:00 2001 From: Huanqi Cao Date: Sat, 4 Jul 2026 23:10:37 +0800 Subject: [PATCH] fix(scripts): handle .cmd bin shims on Windows (CVE-2024-27980) execFileSync on a .cmd shim returns EINVAL on recent Node without shell:true. Same bug class as install-lefthook.mjs. Affected publint-all.ts and verify-node-next-types.ts. (cherry picked from commit 5ae40bee1c840fbbdd197ee15907aa660a343c52) --- scripts/publint-all.ts | 9 ++++++++- scripts/verify-node-next-types.ts | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/publint-all.ts b/scripts/publint-all.ts index bce8c31dd4..722cf4d2ac 100644 --- a/scripts/publint-all.ts +++ b/scripts/publint-all.ts @@ -6,12 +6,18 @@ import { promisify } from 'node:util' const execFileAsync = promisify(execFile) const CONCURRENCY_ENV = 'DSH_PUBLINT_CONCURRENCY' +const isWindows = process.platform === 'win32' // Discover harness packages at packages//; group containers, // examples, and private vendored sources are not package targets. const root = resolve(import.meta.dirname, '..') const packagesRoot = resolve(root, 'packages') +// On Windows recent Node (CVE-2024-27980) refuses to launch .cmd/.bat bin +// shims without shell:true. Use the absolute path to the .cmd shim so the +// subprocess (not a pnpm child — PATH lacks node_modules/.bin) still finds it. +const publintBin = resolve(root, `node_modules/.bin/publint${isWindows ? '.cmd' : ''}`) + type PublintResult = | { path: string; status: 'passed'; stdout: string; stderr: string } | { path: string; status: 'failed'; stdout: string; stderr: string; message: string } @@ -50,10 +56,11 @@ function outputText(value: unknown): string { async function runPublint(path: string): Promise { try { - const { stdout, stderr } = await execFileAsync('node_modules/.bin/publint', [path], { + const { stdout, stderr } = await execFileAsync(publintBin, [path], { cwd: root, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, + shell: isWindows, }) return { path, status: 'passed', stdout, stderr } } catch (error: unknown) { diff --git a/scripts/verify-node-next-types.ts b/scripts/verify-node-next-types.ts index 7883a855c3..7ee046c7bb 100644 --- a/scripts/verify-node-next-types.ts +++ b/scripts/verify-node-next-types.ts @@ -10,6 +10,7 @@ import { execFileSync } from 'node:child_process' import { existsSync, globSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' +const isWindows = process.platform === 'win32' const root = resolve(import.meta.dirname, '..') interface ExportTarget { @@ -143,9 +144,13 @@ try { .join('\n') writeFileSync(resolve(tmp, 'index.ts'), `${imports}\n`) - execFileSync(resolve(root, 'node_modules/.bin/tsc'), ['-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], { + // On Windows the bin shim is a .cmd file; recent Node (CVE-2024-27980) + // refuses to launch .cmd/.bat via execFileSync without shell:true. + const tscBin = isWindows ? resolve(root, 'node_modules/.bin/tsc.cmd') : resolve(root, 'node_modules/.bin/tsc') + execFileSync(tscBin, ['-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], { cwd: root, stdio: 'pipe', + shell: isWindows, }) console.log(`verify-node-next-types: ${packages.length} workspace package declaration surface(s) compile under NodeNext.`) } catch (error: unknown) {