mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 16:02:56 +00:00
* fix(ssh): keep remote Windows commands inside cmd.exe's command-line limit Windows OpenSSH runs every exec request through sshd's DefaultShell, which is cmd.exe on a stock install, and cmd.exe refuses a line over 8191 characters with exit 1 and a localized "The command line is too long". `-EncodedCommand` spends 2.67 characters per script character, so five commands on the first-connect path were already over: the stale upload-stage recovery that opens a fresh install (23,210), promote (20,646), cleanup (19,798), the install-lock steal (11,798) and reserve (9,434). A Windows-to-Windows `ssh:connect` died on the first of them before the relay was ever uploaded (#16126). powerShellCommand now falls back to a gzip self-extracting bootstrap once the inline form passes the budget - these scripts are repetitive enough that the worst one lands at 6.5KB - and throws a message naming the limit if even that cannot fit, rather than letting cmd.exe answer in the host's locale. Commands that already fit are byte-identical. The real-binary PowerShell suite in ssh-relay-upload-stage-commands.test.ts exercises the bootstrap end to end, including `exit` and here-string semantics through Invoke-Expression. * fix(ssh): cite the real command-line budget and reuse the cmd.exe ceiling
60 lines
3.0 KiB
TypeScript
60 lines
3.0 KiB
TypeScript
import { gunzipSync, gzipSync } from 'node:zlib'
|
|
import { encodePowerShellCommand } from '../../shared/powershell-command-encoding'
|
|
import { CMD_EXE_COMMAND_LINE_MAX_CHARS } from '../providers/windows-shell-args'
|
|
export {
|
|
quotePowerShellLiteral as powerShellLiteral,
|
|
quotePowerShellNativeArgument as powerShellNativeArg
|
|
} from '../../shared/powershell-native-argument'
|
|
|
|
// Why cmd.exe and not the 32767 CreateProcess cap: Windows OpenSSH runs every exec request
|
|
// through sshd's DefaultShell, cmd.exe on a stock install. Budget under cmd.exe's own ceiling
|
|
// to leave room for the `/c` wrapper sshd adds before cmd.exe counts the line.
|
|
const WINDOWS_REMOTE_COMMAND_LINE_BUDGET_CHARS = 8_000
|
|
|
|
export function powerShellCommand(script: string): string {
|
|
const inline = encodedPowerShellCommand(script)
|
|
if (inline.length <= WINDOWS_REMOTE_COMMAND_LINE_BUDGET_CHARS) {
|
|
return inline
|
|
}
|
|
// Why: these scripts are repetitive enough that gzip beats the UTF-16LE tax by
|
|
// ~4x, which is the difference between a line cmd.exe runs and one it refuses.
|
|
const compressed = encodedPowerShellCommand(selfExtractingPowerShellScript(script))
|
|
if (compressed.length > WINDOWS_REMOTE_COMMAND_LINE_BUDGET_CHARS) {
|
|
throw new Error(
|
|
`Remote Windows command needs ${compressed.length} characters; Orca budgets ${WINDOWS_REMOTE_COMMAND_LINE_BUDGET_CHARS} for a line sshd hands to cmd.exe, which itself refuses more than ${CMD_EXE_COMMAND_LINE_MAX_CHARS}.`
|
|
)
|
|
}
|
|
return compressed
|
|
}
|
|
|
|
function encodedPowerShellCommand(script: string): string {
|
|
return `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encodePowerShellCommand(script)}`
|
|
}
|
|
|
|
/** Orca-prefixed names so the payload can never shadow the bootstrap's own state. */
|
|
function selfExtractingPowerShellScript(script: string): string {
|
|
const payload = gzipSync(Buffer.from(script, 'utf-8'), { level: 9 }).toString('base64')
|
|
return [
|
|
`$OrcaScriptBytes = [Convert]::FromBase64String('${payload}')`,
|
|
'$OrcaScriptMemory = New-Object System.IO.MemoryStream -ArgumentList (,$OrcaScriptBytes)',
|
|
'$OrcaScriptGzip = New-Object System.IO.Compression.GZipStream -ArgumentList $OrcaScriptMemory, ([System.IO.Compression.CompressionMode]::Decompress)',
|
|
'$OrcaScriptReader = New-Object System.IO.StreamReader -ArgumentList $OrcaScriptGzip, ([System.Text.Encoding]::UTF8)',
|
|
'$OrcaScriptText = $OrcaScriptReader.ReadToEnd()',
|
|
'$OrcaScriptReader.Dispose()',
|
|
'Invoke-Expression $OrcaScriptText'
|
|
].join('\n')
|
|
}
|
|
|
|
/** Inverse of `powerShellCommand`: the script the host will actually run. */
|
|
export function decodeRemotePowerShellScript(command: string): string {
|
|
const encoded = command.match(/-EncodedCommand\s+([A-Za-z0-9+/=]+)/u)?.[1]
|
|
if (!encoded) {
|
|
return command
|
|
}
|
|
const script = Buffer.from(encoded, 'base64').toString('utf16le')
|
|
const payload = script.match(
|
|
/^\$OrcaScriptBytes = \[Convert\]::FromBase64String\('([A-Za-z0-9+/=]+)'\)/u
|
|
)?.[1]
|
|
return payload ? gunzipSync(Buffer.from(payload, 'base64')).toString('utf-8') : script
|
|
}
|