Files
orca/src/main/ssh/ssh-relay-windows-launch-command.test.ts
T
OrcaWinandm4air 6d1a97ef98 fix(ssh): launch the Windows relay outside sshd's job so standard users work (#24224)
* fix(ssh): launch the Windows relay outside sshd's job without WMI

Win32-OpenSSH kills a session's job on close but allows breakaway. relay.js
gains a one-shot launcher mode that starts the detached relay with
CREATE_BREAKAWAY_FROM_JOB through the staged process-tree addon, so a standard
user no longer needs a WMI Remote Enable grant. WMI stays as the fallback for a
relay without the addon, and a refusal there is named. The Windows SSH-host
lanes drop their WMI grant and assert the breakaway route and adoption.

* fix(ssh): find runtime holds without WMI on a standard-user Windows host

The store GC read held runtimes through Get-CimInstance Win32_Process, which
WMI refuses to a standard user's SSH logon, so the pass kept every runtime.
On a refusal it now reads this account's own process image paths through
Get-Process.

* build(relay): ship the Windows relay launcher addon in every desktop package

macOS and Linux packages carried Windows relays without windows-process-tree.node,
so a legacy-runtime relay they uploaded to a Windows SSH host could not launch
outside sshd's job and fell back to WMI, which a standard user is refused.

A reusable Windows job now compiles the x64 and arm64 addons once and uploads
them; release-cut, release-mac-build, and the hourly/daily/adhoc mac builds
download them before build:release and require both arches. Staging now rejects
a binary with the wrong PE machine, the ReadProcessMemory import, or no
spawnOutsideJob export, so a stale pre-launcher build cannot ship.

* ci(ssh): run the Windows SSH-host lanes when the relay process-tree build scripts change

The staging and gyp-rebuild scripts decide which windows-process-tree addon the
relay ships, so a change to either must re-prove the Windows host cells.

* test(ci): find the mac orcad-template download by artifact name

The release mac job now also downloads the relay Windows process-tree addons, so
the first download-artifact step is no longer the template's.

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-10-01 05:32:09 -07:00

114 lines
5.0 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
formatRelayWindowsLaunchReport,
parseRelayWindowsLaunchReport,
RELAY_WINDOWS_BREAKAWAY_LAUNCH_FLAG
} from '../../shared/relay-windows-breakaway-launch'
import {
classifyWindowsRelayLaunchError,
WINDOWS_RELAY_LAUNCH_REFUSED_MARKER,
windowsRelayLaunchCommand
} from './ssh-relay-windows-launch-command'
import { getRemoteHostPlatform } from './ssh-remote-platform'
import { decodeRemotePowerShellScript } from './ssh-remote-powershell'
const host = getRemoteHostPlatform('win32-x64')
const opts = {
nodePath: 'C:/Users/me user/.orca-remote/runtimes/node-abc/node.exe',
remoteDir: 'C:/Users/me user/.orca-remote/relay-1',
sockPath: '\\\\.\\pipe\\orca-relay-1',
endpointDir: 'C:/Users/me user/.orca-remote/relay-1/agent-hooks/orca-relay-1',
graceTime: 300,
logFile: 'C:/Users/me user/.orca-remote/relay-1/relay.log',
errFile: 'C:/Users/me user/.orca-remote/relay-1/relay.err.log',
credentialFile: 'C:/Users/me user/.orca-remote/relay-1/orca-relay-1.credential'
}
function launchScript(): string {
return decodeRemotePowerShellScript(windowsRelayLaunchCommand(host, opts))
}
describe('windowsRelayLaunchCommand', () => {
it('starts the relay through the breakaway launcher on the same node.exe', () => {
const script = launchScript()
const launcher = `& '${opts.nodePath}' relay.js '${RELAY_WINDOWS_BREAKAWAY_LAUNCH_FLAG}' '--stdout-file' '${opts.logFile}' '--stderr-file' '${opts.errFile}' '--relay-args' '--detached' '--grace-time' '300' '--sock-path' '${opts.sockPath}'`
expect(script).toContain(launcher)
expect(script.indexOf(launcher)).toBeLessThan(script.indexOf('Invoke-CimMethod'))
})
it('reaches WMI only when the launcher reports itself unavailable', () => {
const script = launchScript()
const wmiBranch = script.slice(script.indexOf('if ($orcaLaunchCode -eq 3)'))
expect(wmiBranch.indexOf('Invoke-CimMethod')).toBeGreaterThan(0)
expect(wmiBranch.indexOf('Invoke-CimMethod')).toBeLessThan(wmiBranch.indexOf('elseif'))
expect(script).toContain(
`"C:/Users/me user/.orca-remote/relay-1/relay.js" --detached --grace-time 300`
)
expect(script).toContain(`1>"${opts.logFile}" 2>"${opts.errFile}"`)
})
it('names a WMI refusal so a standard-user host fails with a reason', () => {
const script = launchScript()
expect(script).toContain(`throw "${WINDOWS_RELAY_LAUNCH_REFUSED_MARKER}:`)
expect(script).toContain('Invoke-CimMethod -ErrorAction Stop')
})
it('emits nothing an EDR scores as a launch technique', () => {
const script = launchScript()
expect(script).not.toMatch(/Add-Type|ExecutionPolicy|Register-ScheduledTask|schtasks/iu)
})
it('passes the uploaded ripgrep to both routes', () => {
const script = decodeRemotePowerShellScript(
windowsRelayLaunchCommand(host, { ...opts, ripgrepPath: 'C:/rg/rg.exe' })
)
expect(script).toContain(`'--ripgrep-path' 'C:/rg/rg.exe'`)
expect(script).toContain('--ripgrep-path "C:/rg/rg.exe"')
})
})
describe('classifyWindowsRelayLaunchError', () => {
it('turns a refusal into a named error', () => {
const exec = new Error(
`Command "powershell.exe -EncodedCommand AAAA" failed (exit 1): ${WINDOWS_RELAY_LAUNCH_REFUSED_MARKER}: this account cannot start a process that outlives the SSH session: breakaway launcher unavailable (ORCA_RELAY_LAUNCH {"method":"unavailable","reason":"addon-missing"}) and WMI Win32_Process.Create denied (Access denied)\nAt line:1 char:1`
)
const classified = classifyWindowsRelayLaunchError(exec)
if (!(classified instanceof Error)) {
throw new Error('expected an Error')
}
expect(classified.message).toMatch(
/^The Windows host refused to start Orca's relay outside the SSH session\. ORCA_RELAY_LAUNCH_REFUSED: .*addon-missing.*Access denied\)$/u
)
expect(classified.message).not.toContain('EncodedCommand')
})
it('leaves every other launch failure as it was', () => {
const exec = new Error('Command "x" failed (exit 1): Relay launcher exited 1')
expect(classifyWindowsRelayLaunchError(exec)).toBe(exec)
})
})
describe('parseRelayWindowsLaunchReport', () => {
it('reads the launcher and WMI reports', () => {
const breakaway = formatRelayWindowsLaunchReport({ method: 'breakaway', pid: 7, inJob: false })
expect(parseRelayWindowsLaunchReport(`noise\r\n${breakaway}\r\n`)).toEqual({
method: 'breakaway',
pid: 7,
inJob: false
})
const unavailable = formatRelayWindowsLaunchReport({
method: 'unavailable',
reason: 'addon-missing'
})
expect(
parseRelayWindowsLaunchReport(`${unavailable}\nORCA_RELAY_LAUNCH {"method":"wmi"}`)
).toEqual({ method: 'wmi' })
})
it('reads nothing from output without a report', () => {
expect(parseRelayWindowsLaunchReport('')).toBeNull()
expect(parseRelayWindowsLaunchReport('ORCA_RELAY_LAUNCH not-json')).toBeNull()
expect(parseRelayWindowsLaunchReport('ORCA_RELAY_LAUNCH {"method":"breakaway"}')).toBeNull()
})
})