Fix: keep the node runtime probe clear of cmd.exe metacharacters (#458)

Volta's Windows shims exec through `cmd /C`, which re-parses the argument
list, so the `>=` inside the probe's `node -e` payload was read as output
redirection. The command died with "The filename, directory name, or volume
label syntax is incorrect" before node started, the guard read that as a
missing runtime, and the hook it exists to protect was disabled on every
PostToolUse and Stop. A user on a supported Node 24 got a one-time notice
telling them to install Node 22, then silence.

Clamping with Math.min is the same floor test in the same ES5-only syntax,
with no character cmd.exe can claim. Verified through the Volta shim on Node
24.16.0 and 22.18.0 (exit 0) and against a real Node 20.6.1 binary (exit 1),
so the floor is unchanged. Adds a regression test asserting no `<`, `>`, or
newline reaches any generated `node -e` payload.

Upstream cause: volta-cli/volta#1791.

Prepared with AI assistance (Claude Code).
This commit is contained in:
Rex Lorenzo
2026-08-03 15:02:29 -07:00
committed by GitHub
parent e2761cae80
commit 14d2641685
8 changed files with 26 additions and 12 deletions
+9 -1
View File
@@ -34,7 +34,7 @@ const ENGINES_NODE_MAJOR = parseInt(
JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'package.json'), 'utf8')).engines.node.replace(/[^\d.]/g, ''),
10,
);
const NODE_PROBE = `process.exit(parseInt(process.versions.node,10)>=${ENGINES_NODE_MAJOR}?0:1)`;
const NODE_PROBE = `process.exit(Math.min(parseInt(process.versions.node,10),${ENGINES_NODE_MAJOR})===${ENGINES_NODE_MAJOR}?0:1)`;
function expectCommand(command, expectedPath) {
assert.equal(typeof command, 'string');
@@ -226,6 +226,14 @@ describe('hook manifest builders', () => {
}
});
// Volta's Windows shims exec through `cmd /C`, which claims `<`, `>`, and
// newlines from the `node -e` payload, so the probe died before node ran and
// the guard read that as a missing runtime (volta-cli/volta#1791). Every
// command is asserted to carry NODE_PROBE above, so this covers them all.
it('keeps the runtime probe free of characters cmd.exe re-parses', () => {
assert.ok(!/[<>\n]/.test(NODE_PROBE), `cmd.exe-unsafe character in probe: ${NODE_PROBE}`);
});
it('routes supported hook builders and leaves other providers alone', () => {
assert.ok(hooksJsonFor('claude'));
assert.ok(hooksJsonFor('codex'));