Files
orca/src/shared/git-ssh-policy-env.ts
T
NeilandClaude 6328a104bd refactor(ssh): split ssh-connection-utils into concretely named modules (#30488)
* refactor(ssh): split ssh-connection-utils into concretely named modules

- classifiers -> ssh-reconnect-error-classification.ts
- retry/timeout constants and sleep -> connection/ssh-connection-timing.ts
- buildConnectConfig -> connection/ssh-connect-config.ts
- credential callbacks -> connection/ssh-connection-callbacks.ts
- exec options and abort error -> connection/ssh-exec-options.ts
- wrapRemoteCommandForPosixShell -> ssh-posix-command-wrapper.ts
- shellEscape and shared/wsl-login-shell-command quotePosixShell (identical) -> shared/shell-argument-quoting.ts quotePosixShell

No re-exports are left behind.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: point the WSL git benchmark and the WSL lane route at shell-argument-quoting

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 20:48:05 -07:00

155 lines
4.6 KiB
TypeScript

import { isWindowsAbsolutePathLike } from './cross-platform-path'
import { quotePosixShell } from './shell-argument-quoting'
export type GitSshPolicyMode =
| 'default'
| 'explicit-env'
| 'fallback'
| 'configured-openssh'
| 'configured-wrapper-passthrough'
export const GIT_SSH_CONFIG_ARGS = [
'config',
'--null',
'--get-regexp',
'^(core\\.sshcommand|ssh\\.variant)$'
]
export function parseGitSshConfig(stdout: string): { command: string; variant?: string } {
let command = ''
let variant: string | undefined
for (const entry of stdout.split('\0')) {
const separator = entry.indexOf('\n')
const key = entry.slice(0, separator)
const value = entry.slice(separator + 1)
if (key === 'core.sshcommand') {
command = value
}
if (key === 'ssh.variant') {
variant = value
}
}
return { command, variant }
}
function commandBasename(command: string): string {
const pieces = command.split(/[\\/]+/)
return pieces.at(-1)?.toLowerCase() ?? command.toLowerCase()
}
function isMergeableOpenSshCommand(command: string): boolean {
const basename = commandBasename(command)
return basename === 'ssh' || basename === 'ssh.exe'
}
function containsShellExpansionSyntax(command: string): boolean {
return /[$#*?[\]{}\r\n]/.test(command) || /(?:^|\s)['"]~/.test(command) || command.includes('\\~')
}
function containsShellControlSyntax(command: string): boolean {
let quote: "'" | '"' | null = null
let escaped = false
for (const char of command) {
if (escaped) {
escaped = false
} else if (quote === "'") {
if (char === quote) {
quote = null
}
} else if (char === '\\') {
escaped = true
} else if (quote === '"') {
if (char === quote) {
quote = null
} else if (char === '`') {
return true
}
} else if (char === "'" || char === '"') {
quote = char
} else if (';&|<>()`'.includes(char)) {
return true
}
}
return escaped || quote !== null
}
function openSshExecutableEnd(command: string): number | null {
const match = /^[ \t]*(?:'([^']*)'|"((?:\\.|[^"\\])*)"|([^ \t'"]+))(?=[ \t]|$)/.exec(command)
const executable = match?.[1] ?? match?.[2] ?? match?.[3] ?? ''
if (
!match ||
(match[3]?.includes('\\') && !isWindowsAbsolutePathLike(executable)) ||
!isMergeableOpenSshCommand(executable)
) {
return null
}
return match[0].length
}
function quoteBareWindowsPathWords(command: string): string {
return command.replace(
/'[^']*'|"(?:\\.|[^"\\])*"|(?:\\.|[^ \t'"\\])+/g,
(word: string, offset: number) => {
const end = offset + word.length
const windowsPath = /(?:^|=|^-[A-Za-z])([A-Za-z]:\\|\\\\)/.exec(word)
if (
word.startsWith("'") ||
word.startsWith('"') ||
(offset > 0 && !/[ \t]/.test(command[offset - 1])) ||
(end < command.length && !/[ \t]/.test(command[end])) ||
!windowsPath
) {
return word
}
const uncPrefixOffset =
windowsPath[1] === '\\\\' ? windowsPath.index + windowsPath[0].length - 2 : -1
return quotePosixShell(
word.replace(/\\([ \t'"\\;&|<>()`])/g, (match, escaped: string, offset: number) =>
offset === uncPrefixOffset && word[offset + 2] !== '\\' ? match : escaped
)
)
}
)
}
function buildOpenSshBatchModeCommand(configuredCommand: string): string | null {
if (
containsShellExpansionSyntax(configuredCommand) ||
containsShellControlSyntax(configuredCommand)
) {
return null
}
const command = quoteBareWindowsPathWords(configuredCommand)
const end = openSshExecutableEnd(command)
if (end === null) {
return null
}
// OpenSSH keeps the first value; avoid rebuilding the configured argument list.
return `${command.slice(0, end)} -o BatchMode=yes${command.slice(end)}`
}
export function buildGitSshPolicyEnv(
env: NodeJS.ProcessEnv,
configuredCommand: string,
configuredVariant?: string
): { env: NodeJS.ProcessEnv; mode: GitSshPolicyMode } {
if (env.GIT_SSH_COMMAND || env.GIT_SSH) {
return { env, mode: 'explicit-env' }
}
if (!configuredCommand) {
return { env: { ...env, GIT_SSH_COMMAND: 'ssh -o BatchMode=yes' }, mode: 'fallback' }
}
const variant = (env.GIT_SSH_VARIANT ?? configuredVariant)?.toLowerCase()
const batchModeCommand =
!variant || variant === 'ssh' || variant === 'auto'
? buildOpenSshBatchModeCommand(configuredCommand)
: null
if (!batchModeCommand) {
return { env, mode: 'configured-wrapper-passthrough' }
}
return {
env: { ...env, GIT_SSH_COMMAND: batchModeCommand },
mode: 'configured-openssh'
}
}