Skip to content

fix(agent): launch stdio MCP servers through cmd.exe and kill their child trees on Windows#201

Open
luminary19 wants to merge 3 commits into
sybil-solutions:mainfrom
luminary19:up/mcp-windows
Open

fix(agent): launch stdio MCP servers through cmd.exe and kill their child trees on Windows#201
luminary19 wants to merge 3 commits into
sybil-solutions:mainfrom
luminary19:up/mcp-windows

Conversation

@luminary19

@luminary19 luminary19 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

stdio MCP servers never started on Windows: their launchers (npx.cmd / uvx.cmd)
are .cmd shims that Node 24 refuses to spawn without a shell, and on close only
the direct child was signalled so the real server process leaked.

On win32 the client now:

  • Resolves the command first (PATH + PATHEXT) and spawns .exe/.com
    targets directly, with no shell — only genuine .cmd/.bat shims are
    routed through cmd.exe.
  • For those shims, builds a single command line under cmd.exe /d /v:off /s /c
    with proper Windows CRT argument quoting plus double caret-escaping and
    windowsVerbatimArguments (so Node does not re-quote). The double escaping is
    required because npm-style shims forward with %*, which triggers a second
    cmd.exe parse — a single-pass escaper is still injectable.
  • Tears the process tree down with taskkill /PID <pid> /T /F.

Other platforms keep the direct spawn + SIGTERM path unchanged. Fixes #200

Security

The first cut launched via cmd.exe /c <command> <args…> with unescaped args,
which a background review correctly flagged as command injection
(CVE-2024-27980 class): an MCP arg such as a&calc.exe, sourced from a
semi-trusted project .mcp.json, would break out. The hardened launcher above
neutralizes & | < > ^ % ! across both cmd.exe parses. Verified live on
Node 24.16 by resolving the real npx.CMD and confirming benign args pass
through while a&calc.exe, x|y, %PATH%, !v! are all caret-escaped to
literals (e.g. a&calc.exea^^^&calc.exe), so calc.exe never executes as
a separate command. (Command authorization — a config naming calc.exe as the
command itself — remains a separate boundary, unchanged here.)

Validation

npm --prefix frontend run typecheck clean (covers the agent-runtime sources);
check:quality (including the standalone build) runs as the pre-push gate. The
Windows launch behavior was verified live on Windows 11: a filesystem MCP server
over npx connects, its tools list and execute, and closing the session removes
the whole process tree.

UI changes

None.

Risks / rollout notes

  • .exe/.com MCP commands now bypass the shell entirely (stricter, correct).
  • Escaping is defensive; it does not authorize which commands may run — that is a
    separate concern for the MCP-config trust flow.

@luminary19
luminary19 requested a review from 0xSero as a code owner July 13, 2026 18:28
Resolve the command via PATH/PATHEXT and spawn .exe/.com directly; only real
.cmd/.bat shims route through cmd.exe, now with /d /v:off /s /c, CRT argument
quoting plus double caret-escaping (npm-style %* shims re-parse args a second
time), and windowsVerbatimArguments so Node does not re-quote. Neutralizes
& | < > ^ % ! injection from semi-trusted MCP args. POSIX spawn is unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] stdio MCP servers fail to spawn on Windows and leak child trees on close

1 participant