Skip to content

Fix Windows backslash paths being mangled when adding an SSH target#14554

Open
sean-mcmanus wants to merge 1 commit into
mainfrom
seanmcm/fixSSHBug
Open

Fix Windows backslash paths being mangled when adding an SSH target#14554
sean-mcmanus wants to merge 1 commit into
mainfrom
seanmcm/fixSSHBug

Conversation

@sean-mcmanus

@sean-mcmanus sean-mcmanus commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Found and fixed using Copilot with Claude Opus 4.8 in VS Code.

Problem

When adding an SSH target via "C/C++: Add SSH Target" (or anywhere sshCommandToConfig runs), the user-entered SSH connection command is tokenized with shell-quote's parse(). On Windows, parse() treats \ as a POSIX escape character and strips it, so any backslash path in the command is corrupted:

Input:  ssh -i C:\Users\me\.ssh\id_rsa user@host
Tokens: ["ssh", "-i", "C:Usersme.sshid_rsa", "user@host"]

The resulting SSH config entry gets a broken IdentityFile (and the same corruption applies to any other path-bearing argument, e.g. a config file or control path). The connection then fails because the key file path no longer exists.

Root cause

shell-quote's parse() follows POSIX shell rules, where \ escapes the next character. Windows paths use \ as the directory separator, so C:\Users\me is read as the escape sequences \U, \m, etc., and the backslashes are consumed.

This is long-standing behavior of parse() and is independent of the recent shell-quote 1.8.2 -> 1.8.4 bump (verified: parse() produces the identical mangled result in both versions). It is a pre-existing bug, surfaced while reviewing the extension's use of shell-quote.

Fix

On Windows only, double the backslashes in the command before passing it to parse(). parse()'s own unescaping then collapses each \\ back to a single \, preserving the original Windows path:

parse(isWindows ? command.replace(/\\/g, '\\\\') : command)

Non-Windows behavior is unchanged (the command is passed straight through), so POSIX paths and POSIX shell escaping are unaffected.

Validation

Verified the tokenizer output with the installed shell-quote for several inputs:

  • ssh -i C:\Users\me\key.pem user@host -> path preserved as C:\Users\me\key.pem
  • ssh -i "C:\Program Files\me\key.pem" user@host -> quoted path with a space preserved as C:\Program Files\me\key.pem
  • ssh hello@microsoft.com -A -> unchanged (no paths)
  • ssh -i /home/me/.ssh/id_rsa user@host -> unchanged (forward slashes)

@sean-mcmanus sean-mcmanus requested a review from a team as a code owner June 29, 2026 15:15
@github-project-automation github-project-automation Bot moved this to Pull Request in cpptools Jun 29, 2026
@sean-mcmanus sean-mcmanus requested a review from Copilot June 29, 2026 15:15
@sean-mcmanus sean-mcmanus added this to the 1.33.3 milestone Jun 29, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a Windows-specific issue where shell-quote.parse() mangles backslash-based paths (e.g., C:\Users\...) when converting a user-entered SSH command into an SSH config entry, which can break IdentityFile and other path-bearing arguments.

Changes:

  • Adds Windows OS detection via isWindows.
  • On Windows, pre-processes the SSH command by doubling backslashes before passing it to shell-quote.parse() to preserve Windows paths during tokenization.

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

Comment on lines +128 to +131
// shell-quote's parse() treats '\' as a POSIX escape character and strips it, which mangles
// Windows paths (e.g. '-i C:\Users\me\key' becomes 'C:Usersmekey'). On Windows, double the
// backslashes first so parse()'s unescaping restores the original single backslashes.
const parts: string[] = parse(isWindows ? command.replace(/\\/g, '\\\\') : command) as string[];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Pull Request

Development

Successfully merging this pull request may close these issues.

2 participants