Files
orca/src/main/ssh/ssh-remote-windows-command-line-limit.test.ts
T
Neil e4871b12fc fix(ssh): move Windows file writes off PowerShell 5.1 stdin onto sftp
#16432 was fixed by chunking writes to 32KB, on the belief that a
`DefaultShell=cmd.exe` host caps one stdin at roughly 50KB. Re-measured on
Windows 11 26200.9168 / OpenSSH_for_Windows_10.0p2, that premise is wrong in
both directions, and the chunking does not fix the hang.

The real constraint: a read on Windows PowerShell 5.1's redirected-stdin handle
over a non-pty ssh exec can die permanently when it finds the stream momentarily
empty, taking both the remaining data and the EOF with it. It is probabilistic
per such read — not a size threshold, and not certain on the first one. Measured
by swapping the copy loop for a counting reader:

  a 1.5s gap before any byte    -> 0 bytes received, 6 of 6
  1 byte, 1.5s gap, then 32767  -> exactly 1 byte
  32768, 1.5s gap, then 32768   -> exactly 32768
  a continuous 2MB              -> 167936 / 270336 / 372736

Those three 2MB figures are one payload run three times under the same
conditions, which is what rules out a threshold. Independently reproduced by a
second harness where one 1.9MB counted read completed through 39 reads and
another died after 11.

A payload that fits one burst usually presents only one read that can find the
stream empty, which is why 32KB mostly works — and it still failed 15 times in
120 under load, and 1 in 40 on a quiet host. Neither rate survives the 62 execs
a 1.9MB file needs: even 2.5% compounds to about four uploads in five failing.
No chunk size helps, because the defect is per blocking read, not per byte.
Three controls on the same host, same DefaultShell, rule out both a size limit
and cmd.exe: `findstr` took 2,016,000 bytes through one exec's stdin, sftp moved
1.9MB 5/5, and PowerShell 7 took 2MB in one exec.

Windows writes now go over the sftp subsystem, whose batch script is read by
the *local* client, so no remote process reads a pipe at all. PowerShell 7 is
the fallback where sftp is unavailable, and Windows PowerShell 5.1 is last,
still bounded, and now reports the host limitation and its remedy instead of a
bare timeout.

Measured on the same host, through this code: 1.9MB x20 all succeeded,
hash-verified, median 315ms, against 0/6 before. 32KB x120 zero hangs, against
15/120.

Also:
- Stage under a unique name per attempt. An abandoned write leaves a remote
  process that may still hold the staging file, and losing contact is not
  evidence it died (docs/reference/ssh-execution-boundary.md), so a retry must
  not reuse a name its predecessor may own. Sweep is best-effort and never
  treated as proof of anything.
- Create upload directories over sftp too; the JSON mkdir batch rode the same
  defective read.
- Cover makeWindowsWriteFileCommand and the publish command against the
  8000-char budget, which F11 flagged as untested.
2026-09-04 00:35:22 -07:00

123 lines
5.2 KiB
TypeScript

import { gunzipSync } from 'node:zlib'
import { describe, expect, it } from 'vitest'
import { CMD_EXE_COMMAND_LINE_MAX_CHARS } from '../providers/windows-shell-args'
import { getRemoteHostPlatform } from './ssh-remote-platform'
import { tryStealInstallLockCommand } from './ssh-relay-install-lock-commands'
import { decodeRemotePowerShellScript, powerShellCommand } from './ssh-remote-powershell'
import {
makeWindowsPublishStagedFileCommand,
makeWindowsWriteFileCommand
} from './system-ssh-windows-file-write'
import {
cleanupOwnedRelayUploadStageCommand,
promoteOwnedRelayUploadStageCommand,
recoverOneStaleRelayUploadStageCommand,
reserveRelayUploadStageCommand,
type RelayUploadStageSlot
} from './ssh-relay-upload-stage-commands'
const windows = getRemoteHostPlatform('win32-x64')
const owner = '.sftp-namespace-123e4567e89b12d3a456426614174000'
const pool = 'C:\\Users\\orca\\.orca-remote\\.upload-stages'
const stage: RelayUploadStageSlot = {
poolDir: pool,
slotName: 'slot-0',
slotDir: `${pool}\\slot-0`,
claimDir: `${pool}\\claim-0`,
deleteDir: `${pool}\\delete-0`
}
// Why: sshd runs an exec request through its DefaultShell, which is cmd.exe on a
// stock Windows OpenSSH install, and cmd.exe refuses a longer line with exit 1
// and a localized "The command line is too long" — the whole connect dies there.
describe('Windows remote command line limit', () => {
it.each([
['recover stale upload stage', recoverOneStaleRelayUploadStageCommand(windows, pool)],
['reserve upload stage', reserveRelayUploadStageCommand(windows, pool, owner)],
[
'promote upload stage',
promoteOwnedRelayUploadStageCommand(windows, stage, owner, 'C:\\Users\\orca\\.orca-remote')
],
['cleanup upload stage', cleanupOwnedRelayUploadStageCommand(windows, stage, owner)],
[
'steal stale install lock',
tryStealInstallLockCommand(windows, 'C:\\Users\\orca\\.orca-remote\\relay', 1_200)
],
// F11 flagged these two as uncovered. They carry one path literal each, so they are the file
// commands whose length a caller can actually move.
['write file', makeWindowsWriteFileCommand('C:\\Users\\orca\\.orca-remote\\relay.js')],
[
'publish staged file',
makeWindowsPublishStagedFileCommand(
'C:\\Users\\orca\\.orca-remote\\relay.js.orca-partial-0123456789ab',
'C:\\Users\\orca\\.orca-remote\\relay.js',
'create'
)
]
])('keeps the %s command inside what sshd\u2019s cmd.exe accepts', (_name, command) => {
expect(command.length).toBeLessThanOrEqual(CMD_EXE_COMMAND_LINE_MAX_CHARS)
})
it('leaves a command that already fits byte-identical', () => {
const script = "Write-Output ([Environment]::GetFolderPath('UserProfile'))"
expect(decodeRemotePowerShellScript(powerShellCommand(script))).toBe(script)
})
it('carries an oversized script through gzip without altering it', () => {
const script = Array.from(
{ length: 200 },
(_unused, index) => `Write-Output ${index}; $slot = 'C:\\Users\\orca\\stage-${index}'`
).join('\n')
const command = powerShellCommand(script)
expect(command.length).toBeLessThanOrEqual(CMD_EXE_COMMAND_LINE_MAX_CHARS)
expect(decodeRemotePowerShellScript(command)).toBe(script)
const bootstrap = Buffer.from(
command.match(/-EncodedCommand\s+([A-Za-z0-9+/=]+)$/u)?.[1] ?? '',
'base64'
).toString('utf16le')
const payload = bootstrap.match(/FromBase64String\('([A-Za-z0-9+/=]+)'\)/u)?.[1] ?? ''
expect(gunzipSync(Buffer.from(payload, 'base64')).toString('utf-8')).toBe(script)
expect(bootstrap).toContain('Invoke-Expression $OrcaScriptText')
})
it('refuses a script no encoding can fit instead of letting cmd.exe reject it', () => {
let seed = 12345
const incompressible = Array.from({ length: 60_000 }, () => {
seed = (seed * 1103515245 + 12345) % 2147483648
return String.fromCharCode(97 + (seed % 26))
}).join('')
expect(() => powerShellCommand(`Write-Output '${incompressible}'`)).toThrow(
/Orca budgets 8000 for a line sshd hands to cmd\.exe/u
)
})
})
/**
* F11 asked whether a pathological path could reach the budget, and what happens if it does.
* Measured: the inline encoding crosses 8000 at roughly 2500 high-entropy path characters — an
* order of magnitude past what Windows itself accepts — and the failure is a throw before any ssh
* is spawned, never a hang.
*/
describe('Windows file command budget headroom', () => {
it('absorbs a path far longer than Windows will accept', () => {
const deep = `C:\\Users\\orca\\${'segment\\'.repeat(30)}relay.js`
expect(deep.length).toBeGreaterThan(260)
expect(makeWindowsWriteFileCommand(deep).length).toBeLessThanOrEqual(
CMD_EXE_COMMAND_LINE_MAX_CHARS
)
})
it('throws rather than spawning a line cmd.exe would refuse', () => {
// Random segments so gzip cannot rescue it, which is the only way to reach the ceiling at all.
const incompressible = Array.from(
{ length: 400 },
(_unused, index) => `${index}-${Math.random().toString(36).slice(2)}`
).join('\\')
expect(() => makeWindowsWriteFileCommand(`C:\\${incompressible}\\f.bin`)).toThrow(
/Orca budgets 8000/
)
})
})