Skip to content

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

Merged
sean-mcmanus merged 7 commits into
mainfrom
seanmcm/fixSSHBug
Jun 30, 2026
Merged

Fix Windows backslash paths being mangled when adding an SSH target#14554
sean-mcmanus merged 7 commits 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. parse() is the wrong tool for splitting an SSH command into arguments: it treats \ as an escape regardless of platform.

Fix

Replace shell-quote's parse() with a small, self-contained tokenizer (splitArgs in sshCommandToConfig.ts) that behaves consistently on every platform: single and double quotes group their contents and are stripped, and unquoted whitespace separates arguments. Outside of quotes, a backslash escapes only a following whitespace character (so a Unix path like /home/me/my\ key keeps its space), but before any other character it stays literal (so Windows paths like C:\Users\me\key are preserved). This keeps the cross-platform behavior the original parse()-based code had, minus the bug where every backslash was treated as an escape.

This replaces the entire parse(...) call:

const parts: string[] = splitArgs(command);

The unused shell-quote and isWindows imports were removed from sshCommandToConfig.ts. This was the last parse() consumer, and the only other consumer (quote() in settings.ts) was replaced with an equivalent string, so shell-quote is removed entirely: dropped from package.json, yarn.lock, and ThirdPartyNotices.txt.

Alternatives considered

Doubling every \ before calling parse() over-escapes backslashes inside single-quoted segments (which shell-quote preserves literally), turning 'C:\Users\me\key' into C:\\Users\\me\\key. Reusing the shared extractArgs helper does not work either: on Windows it implements cmd-style parsing where single quotes are literal, so ssh -i 'C:\Users\me\key' would keep the surrounding quotes in IdentityFile (and split on spaces inside single-quoted paths). A dedicated tokenizer avoids both problems.

Validation

Covered by new unit tests in test/unit/sshCommandToConfig.test.ts (splitArgs tokenization plus end-to-end sshCommandToConfig IdentityFile parsing):

  • 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
  • ssh -i 'C:\Users\me\key' user@host -> single quotes stripped, path preserved
  • ssh -i /home/me/key\ with\ spaces user@host -> backslash-escaped spaces kept
  • "C:\Program Files\" next -> quoted trailing backslash stays literal, not joined to the next arg
  • \\server\share -> UNC path keeps both leading backslashes
  • 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

This comment was marked as resolved.

Comment thread Extension/src/SSH/sshCommandToConfig.ts Outdated
Comment thread Extension/src/LanguageServer/settings.ts Fixed

This comment was marked as resolved.

This comment was marked as resolved.

@sean-mcmanus sean-mcmanus marked this pull request as draft June 29, 2026 23:56

This comment was marked as resolved.

@sean-mcmanus sean-mcmanus requested a review from Copilot June 30, 2026 00:13
@sean-mcmanus sean-mcmanus marked this pull request as ready for review June 30, 2026 00:14

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

Copilot reviewed 4 out of 5 changed files in this pull request and generated no new comments.

Comment thread Extension/src/SSH/sshCommandToConfig.ts
@sean-mcmanus sean-mcmanus merged commit 8e68bbb into main Jun 30, 2026
6 checks passed
@github-project-automation github-project-automation Bot moved this from Pull Request to Done in cpptools Jun 30, 2026
@sean-mcmanus sean-mcmanus deleted the seanmcm/fixSSHBug branch June 30, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants