mirror of
https://github.com/stablyai/orca.git
synced 2026-10-04 08:02:09 +00:00
* feat(ssh): pinned-Node relay on Windows SSH hosts (D5 Windows, D2) Windows hosts opted into remoteRuntime 'pinned-node' now get the same rung A relay POSIX hosts do, instead of an early host-Node fallback. - Runtime store: the official node-v24.21.0-win-<arch>.zip is uploaded to a stage under %USERPROFILE%\.orca-remote\runtimes, verified against the pinned archive hash, node.exe extracted with System32 tar.exe (Expand-Archive fallback), hashed with Get-FileHash, run once, and published with node.exe + .verified by one Directory.Move. One powershell.exe per phase via the existing powerShellCommand helper; the probe also creates the stage. No new -EncodedCommand site, no -ExecutionPolicy, no Add-Type. node.exe keeps its real name at runtimes\node-<sha>\node.exe. - Bytes that change or vanish after Orca wrote and verified them are reported as ORCA_NODE_RUNTIME_SECURITY_MODIFIED and become a remembered 'security_software' refusal (fallback to the host-Node relay); application control blocks classify as 'noexec'. - Addons: the win32 slot's conpty.node, conpty_console_list.node, conpty\conpty.dll + OpenConsole.exe, watcher and windows-process-tree.node ride with the relay; the orcad template now carries the win32 targets. - Self-test on Windows is one powershell.exe running relay.js on node.exe; the report must name the pinned Node. The relay self-test loads conpty.node and opens a PTY with useConptyDll, and reports a missing bundled ConPTY file as a load failure. A pinned relay's terminals use the bundled ConPTY too; host-Node relays are unchanged. - describeRelayRuntime recognizes the Windows store layout. * fix(ssh): skip the redundant stage-cleanup powershell.exe after a Windows runtime promote The promote script already removes its stage on every path, so the client-side cleanup only runs when promote never returned (upload failure, abort, timeout). * test(ssh): expect the ladder's remembered flag and pin check on Windows pinned relays --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
71 lines
2.9 KiB
TypeScript
71 lines
2.9 KiB
TypeScript
/** What the host-side runtime-store scripts print, shared by the POSIX and Windows installers. */
|
|
|
|
export const REMOTE_NODE_RUNTIME_READY = 'ORCA_NODE_RUNTIME_READY'
|
|
export const REMOTE_NODE_RUNTIME_MISSING = 'ORCA_NODE_RUNTIME_MISSING'
|
|
export const REMOTE_NODE_RUNTIME_SELFTEST_FAILED = 'ORCA_NODE_RUNTIME_SELFTEST_FAILED'
|
|
/** A file Orca wrote and verified changed or vanished afterwards; the rest of the line says which. */
|
|
export const REMOTE_NODE_RUNTIME_SECURITY_MODIFIED = 'ORCA_NODE_RUNTIME_SECURITY_MODIFIED'
|
|
export const REMOTE_NODE_RUNTIME_EXIT_PREFIX = 'ORCA_RUNTIME_EXIT='
|
|
export const REMOTE_NODE_RUNTIME_VERIFIED_MARKER = '.verified'
|
|
|
|
/** The pinned runtime ran on the host and did not report its version: a host verdict, with evidence. */
|
|
export class RemoteNodeRuntimeSelfTestError extends Error {
|
|
constructor(
|
|
readonly exitStatus: number | null,
|
|
readonly output: string
|
|
) {
|
|
super(
|
|
`The pinned Node runtime did not run on the host (exit ${exitStatus ?? 'unknown'}): ${output}`
|
|
)
|
|
this.name = 'RemoteNodeRuntimeSelfTestError'
|
|
}
|
|
}
|
|
|
|
/** The host answered, and what it answered is that something rewrote or removed our verified bytes. */
|
|
export class RemoteNodeRuntimeSecurityModifiedError extends Error {
|
|
constructor(readonly detail: string) {
|
|
super(`Security software on the host removed or modified the pinned Node runtime: ${detail}`)
|
|
this.name = 'RemoteNodeRuntimeSecurityModifiedError'
|
|
}
|
|
}
|
|
|
|
/** Splits `ORCA_RUNTIME_EXIT=<n>` from the output that follows it. */
|
|
export function parseRemoteRuntimeExitReport(text: string): {
|
|
exitStatus: number | null
|
|
output: string
|
|
} {
|
|
const lines = text.split(/\r?\n/)
|
|
const index = lines.findIndex((line) => line.startsWith(REMOTE_NODE_RUNTIME_EXIT_PREFIX))
|
|
if (index === -1) {
|
|
return { exitStatus: null, output: text.trim() }
|
|
}
|
|
const status = Number.parseInt(lines[index].slice(REMOTE_NODE_RUNTIME_EXIT_PREFIX.length), 10)
|
|
return {
|
|
exitStatus: Number.isNaN(status) ? null : status,
|
|
output: lines
|
|
.slice(index + 1)
|
|
.join('\n')
|
|
.trim()
|
|
}
|
|
}
|
|
|
|
/** Throws the host's verdict when a promote script reported one instead of READY. */
|
|
export function assertRemoteNodeRuntimePromoted(promoted: string): void {
|
|
const selfTestFailure = promoted.indexOf(REMOTE_NODE_RUNTIME_SELFTEST_FAILED)
|
|
if (selfTestFailure !== -1) {
|
|
const report = parseRemoteRuntimeExitReport(promoted.slice(selfTestFailure))
|
|
throw new RemoteNodeRuntimeSelfTestError(report.exitStatus, report.output)
|
|
}
|
|
const modified = promoted
|
|
.split(/\r?\n/)
|
|
.find((line) => line.startsWith(REMOTE_NODE_RUNTIME_SECURITY_MODIFIED))
|
|
if (modified !== undefined) {
|
|
throw new RemoteNodeRuntimeSecurityModifiedError(
|
|
modified.slice(REMOTE_NODE_RUNTIME_SECURITY_MODIFIED.length).trim()
|
|
)
|
|
}
|
|
if (promoted.trim().split(/\r?\n/).at(-1)?.trim() !== REMOTE_NODE_RUNTIME_READY) {
|
|
throw new Error(`The host did not verify the pinned Node runtime: ${promoted.trim()}`)
|
|
}
|
|
}
|