Files
orca/src/main/ssh/system-ssh-args.ts
T
Jinjing adc020393e feat(ssh): open SSH workspaces in VS Code Remote-SSH (#10005)
Allow the existing "Open in" entries to launch a configured VS Code
launcher against an SSH-backed worktree via Remote-SSH:

    code --remote ssh-remote+<authority> <remote-path>

- Split the blanket SSH/runtime block into a capability model: file
  managers and non-VS Code launchers stay local-only (disabled with
  "Local only" metadata); a recognized VS Code command is enabled and
  forwarded with connectionId over a typed object IPC.
- Main process stays authoritative: rejects active/owned runtimes,
  resolves the SshTarget from the persisted Store, derives the authority
  (config alias, or username@host on port 22, or ssh-alias-required on a
  non-default port), validates POSIX/Windows absolute remote paths without
  local stat/normalize, and rejects non-VS Code and compound commands
  before spawn.
- Authority and remote path are passed as separate argv; getSpawnArgsForWindows
  remains the cmd/bat shim boundary and fails closed on metacharacters.
- Same capability rules across the worktree menu, Explorer overflow, and
  the source-control entry context menu.

Refs STA-2386
Closes #9999
2026-07-22 17:17:59 -07:00

180 lines
6.2 KiB
TypeScript

import type { SshTarget } from '../../shared/ssh-types'
import { getControlSocketPath, type SystemSshResolvedConfig } from './ssh-control-socket'
export type SystemSshBuildArgsOptions = {
resolvedConfig?: SystemSshResolvedConfig | null
disableControlMaster?: boolean
suppressOrcaControlMaster?: boolean
gssapiOnly?: boolean
}
export function buildSshArgs(target: SshTarget, options?: SystemSshBuildArgsOptions): string[] {
const args: string[] = []
args.push('-o', options?.gssapiOnly ? 'BatchMode=yes' : 'BatchMode=no')
if (options?.gssapiOnly) {
// Why: the probe must neither authenticate with a key nor open an OpenSSH
// credential prompt; failure belongs to Orca's existing ssh2 prompt path.
args.push('-o', 'GSSAPIAuthentication=yes')
args.push('-o', 'PreferredAuthentications=gssapi-with-mic')
}
// Forward stdin/stdout for relay communication
args.push('-T')
// Why: ControlMaster multiplexes all SSH exec commands over a single connection,
// eliminating the ~9s handshake overhead per command. Without this, each
// spawnSystemSshCommand call opens a new TCP connection.
const controlPath = getOrcaControlSocketPath(target, options)
const forceDisableControlMaster =
options?.disableControlMaster === true ||
target.systemSshConnectionReuse === false ||
(options?.gssapiOnly === true && controlPath === null)
if (forceDisableControlMaster) {
// Why: muxed OpenSSH forwards remain registered on the master after the
// client exits. Also honors the per-target compatibility opt-out even if
// a broad Host * ssh_config block enables multiplexing.
args.push('-S', 'none')
} else if (controlPath) {
args.push('-o', 'ControlMaster=auto')
args.push('-o', `ControlPath=${controlPath}`)
// Why: keep master alive 300s after last command so rapid reconnects
// (e.g. on tab focus) skip re-handshake without holding a process open.
args.push('-o', 'ControlPersist=300')
args.push('-o', 'ServerAliveInterval=15')
args.push('-o', 'ServerAliveCountMax=3')
}
const useConfigHost = shouldUseOpenSshConfigHost(target)
if (!useConfigHost && target.port !== 22) {
args.push('-p', String(target.port))
}
if (!useConfigHost && target.identityFile) {
args.push('-i', target.identityFile)
}
if (!useConfigHost && target.identityAgent) {
args.push('-o', `IdentityAgent=${target.identityAgent}`)
}
if (!useConfigHost && target.identitiesOnly) {
args.push('-o', 'IdentitiesOnly=yes')
}
if (!useConfigHost && target.gssapiAuthentication && !options?.gssapiOnly) {
// Why: manual targets bypass ssh_config, so Kerberos auth must be
// requested explicitly; config-backed hosts inherit it from their entry.
args.push('-o', 'GSSAPIAuthentication=yes')
}
if (!useConfigHost && target.jumpHost) {
args.push('-J', target.jumpHost)
}
if (!useConfigHost && target.proxyCommand) {
args.push('-o', `ProxyCommand=${target.proxyCommand}`)
}
const host = target.configHost || target.host
const userHost = target.username ? `${target.username}@${host}` : host
args.push('--', userHost)
return args
}
export function getOrcaControlSocketPath(
target: SshTarget,
options?: SystemSshBuildArgsOptions
): string | null {
if (shouldDisableOrcaControlMaster(target, options)) {
return null
}
return getControlSocketPath(target, options?.resolvedConfig, options?.gssapiOnly === true)
}
export function getSystemSshBuildArgsFromOperationOptions(
options: SystemSshBuildArgsOptions | undefined
): SystemSshBuildArgsOptions | undefined {
const buildArgsOptions: SystemSshBuildArgsOptions = {}
if (options?.resolvedConfig !== undefined) {
buildArgsOptions.resolvedConfig = options.resolvedConfig
}
if (options?.disableControlMaster === true) {
buildArgsOptions.disableControlMaster = true
}
if (options?.suppressOrcaControlMaster === true) {
buildArgsOptions.suppressOrcaControlMaster = true
}
if (options?.gssapiOnly === true) {
buildArgsOptions.gssapiOnly = true
}
return Object.keys(buildArgsOptions).length === 0 ? undefined : buildArgsOptions
}
function shouldDisableOrcaControlMaster(
target: SshTarget,
options?: SystemSshBuildArgsOptions
): boolean {
// Why: unresolved ssh_config aliases could otherwise share one Orca socket
// while OpenSSH routes them through mutable HostName/ProxyJump settings.
const unresolvedConfigBackedTarget =
isOpenSshConfigBackedTarget(target) && options?.resolvedConfig == null
return (
options?.disableControlMaster === true ||
options?.suppressOrcaControlMaster === true ||
target.systemSshConnectionReuse === false ||
unresolvedConfigBackedTarget ||
(hasUserConfiguredControlMaster(options?.resolvedConfig) && options?.gssapiOnly !== true)
)
}
function hasUserConfiguredControlMaster(
resolvedConfig: SystemSshResolvedConfig | null | undefined
): boolean {
if (!resolvedConfig) {
return false
}
// Why: ControlPersist/ControlPath alone can reuse a master someone else
// created, but they do not create the setup-burst master Orca needs.
return (
hasEnabledControlMaster(resolvedConfig.controlMaster) &&
hasEnabledControlPath(resolvedConfig.controlPath)
)
}
function hasEnabledControlMaster(value: string | undefined): boolean {
const normalized = value?.trim().toLowerCase()
return (
normalized != null &&
normalized !== '' &&
normalized !== '0' &&
normalized !== 'no' &&
normalized !== 'false'
)
}
function hasEnabledControlPath(value: string | undefined): boolean {
const normalized = value?.trim().toLowerCase()
return normalized != null && normalized !== '' && normalized !== 'none'
}
function shouldUseOpenSshConfigHost(target: SshTarget): boolean {
if (!target.configHost) {
return false
}
return isOpenSshConfigBackedTarget(target)
}
export function isOpenSshConfigBackedTarget(target: SshTarget): boolean {
if (target.source === 'ssh-config') {
return true
}
if (target.source === 'manual') {
return false
}
// Why: legacy imported aliases have a distinct configHost; manual targets
// historically stored configHost=host and still need explicit -p/-i args.
return Boolean(target.configHost && target.configHost !== target.host)
}