mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-07-01 04:21:27 +08:00
fix(hooks): quote args when probing Windows .cmd MCP servers via shell (#2343)
On Windows, when a bare-name MCP server command (e.g. codesys-mcp-sp21-plus) falls back to the .cmd candidate, the probe sets shell:true to work around Node 18.20+ CVE-2024-27980. However, passing an args array alongside shell:true causes Node to concatenate the tokens without quoting (DEP0190), so an arg containing a space (e.g. --codesys-path "C:\Program Files\...") is re-split by cmd.exe at every space boundary. The child process receives a truncated path, fails to launch, and the probe declares the server unavailable, falsely blocking every MCP tool call to that server. Fix: add a quoteWin() helper that double-quotes any token containing whitespace or cmd metacharacters. In the useShell branch, build a single properly-quoted command line string and pass it as the sole argument to spawn() with no separate args array. The else branch (shell:false, all non-.cmd commands) is unchanged. Regression test added: on Windows, creates a .cmd shim that echoes its first positional argument to stderr, probes it with a space-containing path arg, and asserts the probe succeeds and the arg was not split at the space boundary. Co-authored-by: Karstein Phobic Nyvold Kvistad <karstein.kvistad@maritimerobotics.com>
This commit is contained in:
@@ -338,6 +338,21 @@ function probeCommandServer(serverName, config) {
|
||||
// through shell mode.
|
||||
const UNSAFE_SHELL_CHARS = /[&|<>^%!()\s;]/;
|
||||
|
||||
// When spawning via cmd.exe (shell:true) on Windows, Node concatenates
|
||||
// command + args WITHOUT quoting (DEP0190). An arg containing a space —
|
||||
// such as a path under "C:\Program Files" — gets re-split by cmd.exe.
|
||||
// Build a properly-quoted command line instead and pass it as a single
|
||||
// string with no args array, so cmd.exe sees each token as one unit.
|
||||
function quoteWin(token) {
|
||||
// If the token has no characters that need quoting, return it as-is.
|
||||
if (!/[\s"&|<>^%!();]/.test(token)) {
|
||||
return token;
|
||||
}
|
||||
// Escape embedded double quotes by doubling them, then wrap in double
|
||||
// quotes. cmd.exe uses "" as an escaped quote inside a quoted string.
|
||||
return '"' + token.replace(/"/g, '""') + '"';
|
||||
}
|
||||
|
||||
function attempt(idx) {
|
||||
const tryCommand = candidates[idx];
|
||||
const isLast = idx + 1 >= candidates.length;
|
||||
@@ -375,12 +390,26 @@ function probeCommandServer(serverName, config) {
|
||||
|
||||
let child;
|
||||
try {
|
||||
child = spawn(tryCommand, args, {
|
||||
env: mergedEnv,
|
||||
cwd: process.cwd(),
|
||||
stdio: ['pipe', 'ignore', 'pipe'],
|
||||
shell: useShell
|
||||
});
|
||||
if (useShell) {
|
||||
// Build a single quoted command line for cmd.exe. Passing an args
|
||||
// array with shell:true causes Node to concatenate without quoting
|
||||
// (DEP0190), which splits space-containing args (e.g. paths under
|
||||
// "C:\Program Files") at every space boundary.
|
||||
const quotedCmdline = [tryCommand, ...args].map(quoteWin).join(' ');
|
||||
child = spawn(quotedCmdline, {
|
||||
env: mergedEnv,
|
||||
cwd: process.cwd(),
|
||||
stdio: ['pipe', 'ignore', 'pipe'],
|
||||
shell: true
|
||||
});
|
||||
} else {
|
||||
child = spawn(tryCommand, args, {
|
||||
env: mergedEnv,
|
||||
cwd: process.cwd(),
|
||||
stdio: ['pipe', 'ignore', 'pipe'],
|
||||
shell: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if ((error.code === 'ENOENT' || error.code === 'EINVAL') && !isLast) {
|
||||
retryNext();
|
||||
|
||||
Reference in New Issue
Block a user