Skip to content

PDX-504: quote sf executable and args for spaces on Windows#200

Merged
mrdailey99 merged 1 commit into
developfrom
fix/PDX-504-sf-spaces-win32
Jun 2, 2026
Merged

PDX-504: quote sf executable and args for spaces on Windows#200
mrdailey99 merged 1 commit into
developfrom
fix/PDX-504-sf-spaces-win32

Conversation

@mrdailey99
Copy link
Copy Markdown
Collaborator

Summary

  • Fixes the High-severity Windows bug where sf invocation split on spaces. On Windows the sf launcher is a .cmd and must run through cmd.exe (shell: true); Node then concatenates executable + args into one unquoted string, so any space broke the command ('C:\Program' is not recognized ...).
  • This affected the default install casegetSfCommonPaths() auto-discovers C:\Program Files\sf\..., and arg values under a Provar Manager directory also split.
  • Fix: on the win32 shell branch, pre-quote the executable and every argument (quoteWindowsToken). Node joins them and wraps the line in cmd's outer quotes; cmd's /s rule strips only that outermost pair, preserving our per-token quoting. Applied to auto-resolved paths too — not gated on a user-supplied sf_path. The 3-arg spawnSync(executable, args, opts) shape is kept, so space-free tokens are byte-identical (no behavioural change off the spaced-path case).
  • Metacharacter rejection (assertShellSafePathINVALID_SF_PATH) still applies to user-supplied sf_path; spaces are now allowed (and quoted) where previously the 8.3 short-name workaround was needed.

Jira

https://provartesting.atlassian.net/browse/PDX-504

Test plan

  • yarn compile passes
  • yarn test:only passes (1325 passing; 5 new sfSpawn tests)
  • mcp-smoke.cjs passes (58/58)
  • yarn lint passes

Tests (per acceptance criteria, win32 via setSfPlatformForTesting)

  • (a) auto-resolved C:\Program Files\sf\client\bin\sf.cmd is quoted as a single token (not gated on sf_path)
  • (b) explicit spaced sf_path is quoted
  • (c) an argument value containing a space (--properties-file under Provar Manager) is quoted, not split
  • space-free tokens stay unquoted (no regression)
  • a user sf_path with shell metacharacters is still rejected; a spaced-but-clean one is accepted

Changes

  • src/mcp/tools/sfSpawn.ts: quoteWindowsToken helper; quote executable + args on the win32 shell branch.
  • test/unit/mcp/sfSpawn.test.ts: 5 new win32 quoting tests.
  • docs/mcp.md: note that Windows spaced paths are handled automatically (8.3 workaround no longer needed).

RCA: On Windows the sf launcher is a .cmd run via shell:true, where Node concatenates executable + args unquoted, so any space (auto-discovered C:\Program Files\sf install, an explicit sf_path, or a --properties-file value under a Provar Manager directory) split the command — breaking the default Windows install case.
Fix: Pre-quote the executable and every argument with quoteWindowsToken on the win32 shell branch (auto-resolved paths included, not gated on sf_path); Node's join + cmd /s outer-quote strip preserves per-token quoting. Metacharacter rejection still applies to user-supplied paths. Adds win32 unit tests and a docs note.
Copilot AI review requested due to automatic review settings June 2, 2026 19:10
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

Quality Orchestrator

🟢 LOW · 2 / 100 · All changed files have mapped tests.


🧪 Tests to Run · Running 1 of 54 tests

  • unit/mcp/sfSpawn.test.ts
▶ Run command
npx vitest run \
  unit/mcp/sfSpawn.test.ts

⚡ quality-orchestrator  ·  /qo stub <file>  ·  qo analyze-local

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Windows-specific sf subprocess invocation bug where paths/arguments containing spaces were split when using shell: true (required for .cmd launchers). It introduces per-token quoting on the Windows shell path, adds unit coverage for spaced executable/argument cases, and documents that 8.3 short-name workarounds are no longer needed on Windows.

Changes:

  • Add quoteWindowsToken() and apply it to the executable + args when needsWindowsShell(...) is true.
  • Add 5 unit tests validating quoting behavior for spaced auto-resolved paths, explicit sf_path, and spaced argument values.
  • Update MCP docs to clarify Windows spaced paths are handled automatically and metacharacter rejection still applies to user-provided sf_path.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/mcp/tools/sfSpawn.ts Adds Windows token-quoting helper and applies it for shell:true spawns to prevent space-splitting.
test/unit/mcp/sfSpawn.test.ts Adds win32-focused tests verifying executable/args are not split by spaces and metachar injection guard remains.
docs/mcp.md Documents the Windows quoting behavior and removal of the 8.3 short-name workaround requirement.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/mcp/tools/sfSpawn.ts
Comment on lines +190 to +196
function quoteWindowsToken(token: string): string {
if (token === '') return '""';
if (/[\s"&|<>^()]/.test(token)) {
return `"${token.replace(/"/g, '""')}"`;
}
return token;
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — but this is a false positive for cmd.exe, and applying the suggestion would introduce a regression.

In cmd.exe, ^ is an escape character only outside double quotes. Inside a double-quoted token the caret is literal: "abc^" does not let the caret escape the closing quote, and operator parsing is not re-enabled. So quoteWindowsToken wrapping a caret-containing token is already safe.

Escaping ^^^ before wrapping would actively corrupt real inputs: ^ is a legal Windows filename character, so a path such as C:\allowed\weird^name\provardx-properties.json would become weird^^name inside the quotes and fail to resolve. Keeping ^ in the quote-trigger set is in fact the safer choice — an unquoted caret would be the real hazard, and we always quote it.

Injection surface is also nil here: a user-supplied sf_path containing shell metacharacters is rejected up front by assertShellSafePath (INVALID_SF_PATH), and the argument values are internally constructed (file paths already validated against --allowed-paths). Leaving the implementation as-is.

@mrdailey99 mrdailey99 merged commit e262e3e into develop Jun 2, 2026
5 checks passed
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.

2 participants