diff --git a/.github/workflows/ssh-relay-runtime-artifacts.yml b/.github/workflows/ssh-relay-runtime-artifacts.yml index a4427b3fe85..15aec733174 100644 --- a/.github/workflows/ssh-relay-runtime-artifacts.yml +++ b/.github/workflows/ssh-relay-runtime-artifacts.yml @@ -1457,19 +1457,40 @@ jobs: $account = New-LocalUser -Name $fixtureUser -Password $password -PasswordNeverExpires ` -UserMayNotChangePassword -AccountNeverExpires $userSid = $account.SID.Value - $creatorSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value $systemSid = 'S-1-5-18' $administratorsSid = 'S-1-5-32-544' - function Set-FixtureAcl([string]$Path, [string[]]$Grants, [string[]]$ExpectedSids) { - & icacls.exe $Path /inheritance:r | Out-Host - if ($LASTEXITCODE -ne 0) { throw "Failed to remove inherited ACLs from $Path" } - # Why: key generation adds a creator ACE that /grant:r does not replace for other trustees. - & icacls.exe $Path /remove "*$creatorSid" | Out-Host - if ($LASTEXITCODE -ne 0) { throw "Failed to remove creator ACL from $Path" } - $arguments = @($Path, '/grant:r') + $Grants - & icacls.exe @arguments | Out-Host - if ($LASTEXITCODE -ne 0) { throw "Failed to grant fixture ACLs on $Path" } + function New-FixtureAclRule( + [string]$Sid, + [Security.AccessControl.FileSystemRights]$Rights, + [Security.AccessControl.InheritanceFlags]$InheritanceFlags + ) { + $identity = [Security.Principal.SecurityIdentifier]::new($Sid) + return [Security.AccessControl.FileSystemAccessRule]::new( + $identity, + $Rights, + $InheritanceFlags, + [Security.AccessControl.PropagationFlags]::None, + [Security.AccessControl.AccessControlType]::Allow + ) + } + function Set-FixtureAcl( + [string]$Path, + [Security.AccessControl.FileSystemAccessRule[]]$AccessRules, + [string[]]$ExpectedSids + ) { + $item = Get-Item -LiteralPath $Path -Force + $security = if ($item.PSIsContainer) { + [Security.AccessControl.DirectorySecurity]::new() + } else { + [Security.AccessControl.FileSecurity]::new() + } + # Why: replacing the DACL atomically prevents drive-specific defaults from surviving. + $security.SetAccessRuleProtection($true, $false) + foreach ($accessRule in $AccessRules) { + [void]$security.AddAccessRule($accessRule) + } + Set-Acl -LiteralPath $Path -AclObject $security $acl = Get-Acl -LiteralPath $Path $rules = @($acl.Access) if (-not $acl.AreAccessRulesProtected -or @($rules | Where-Object { $_.IsInherited }).Count -ne 0) { @@ -1490,14 +1511,19 @@ jobs: & icacls.exe $Path /setowner "*$OwnerSid" | Out-Host if ($LASTEXITCODE -ne 0) { throw "Failed to set fixture owner on $Path" } } - $systemFull = '*S-1-5-18:(OI)(CI)F' - $administratorsFull = '*S-1-5-32-544:(OI)(CI)F' + $noInheritance = [Security.AccessControl.InheritanceFlags]::None + $childInheritance = [Security.AccessControl.InheritanceFlags]::ContainerInherit -bor ` + [Security.AccessControl.InheritanceFlags]::ObjectInherit $fixtureTrustees = @($systemSid, $administratorsSid, $userSid) - Set-FixtureAcl $fixture @($systemFull, $administratorsFull, "*$userSid`:RX") $fixtureTrustees + Set-FixtureAcl $fixture @( + (New-FixtureAclRule $systemSid FullControl $childInheritance), + (New-FixtureAclRule $administratorsSid FullControl $childInheritance), + (New-FixtureAclRule $userSid ReadAndExecute $noInheritance) + ) $fixtureTrustees Set-FixtureAcl $remoteRoot @( - $systemFull, - $administratorsFull, - "*$userSid`:(OI)(CI)M" + (New-FixtureAclRule $systemSid FullControl $childInheritance), + (New-FixtureAclRule $administratorsSid FullControl $childInheritance), + (New-FixtureAclRule $userSid Modify $childInheritance) ) $fixtureTrustees $clientKey = Join-Path $fixture 'client-key' @@ -1509,14 +1535,14 @@ jobs: $authorizedKeys = Join-Path $fixture 'authorized_keys' Copy-Item -LiteralPath "$clientKey.pub" -Destination $authorizedKeys Set-FixtureAcl $authorizedKeys @( - '*S-1-5-18:F', - '*S-1-5-32-544:F', - "*$userSid`:R" + (New-FixtureAclRule $systemSid FullControl $noInheritance), + (New-FixtureAclRule $administratorsSid FullControl $noInheritance), + (New-FixtureAclRule $userSid Read $noInheritance) ) $fixtureTrustees Set-FixtureOwner $authorizedKeys $userSid Set-FixtureAcl $hostKey @( - '*S-1-5-18:F', - '*S-1-5-32-544:F' + (New-FixtureAclRule $systemSid FullControl $noInheritance), + (New-FixtureAclRule $administratorsSid FullControl $noInheritance) ) @($systemSid, $administratorsSid) Set-FixtureOwner $hostKey 'S-1-5-18' @@ -1638,6 +1664,11 @@ jobs: $ownsFixture = Test-Path -LiteralPath (Join-Path $fixture 'fixture-owned') $ownsService = Test-Path -LiteralPath (Join-Path $fixture 'service-owned') $ownsAccount = Test-Path -LiteralPath (Join-Path $fixture 'account-owned') + $account = if ($ownsAccount) { + Get-LocalUser -Name $fixtureUser -ErrorAction SilentlyContinue + } else { $null } + $userSid = if ($account) { $account.SID.Value } else { $null } + $profilePath = Join-Path $env:SystemDrive "Users/$fixtureUser" try { $service = Get-CimInstance Win32_Service -Filter "Name='$serviceName'" -ErrorAction SilentlyContinue $servicePid = if ($service) { [int]$service.ProcessId } else { 0 } @@ -1665,14 +1696,35 @@ jobs: $log = Join-Path $fixture 'sshd.log' if (Test-Path -LiteralPath $log) { Get-Content -LiteralPath $log -Tail 200 } try { - if ($ownsAccount -and (Get-LocalUser -Name $fixtureUser -ErrorAction SilentlyContinue)) { + if ($ownsAccount -and $userSid) { + $escapedSid = $userSid.Replace("'", "''") + $profileFailure = $null + for ($attempt = 1; $attempt -le 20; $attempt += 1) { + $profile = Get-CimInstance Win32_UserProfile -Filter "SID='$escapedSid'" ` + -ErrorAction SilentlyContinue + if (-not $profile) { break } + try { + Remove-CimInstance -InputObject $profile -ErrorAction Stop + $profileFailure = $null + } catch { + $profileFailure = $_.Exception.Message + } + Start-Sleep -Milliseconds 500 + } + if (Get-CimInstance Win32_UserProfile -Filter "SID='$escapedSid'" ` + -ErrorAction SilentlyContinue) { + throw "Fixture profile did not settle within 10 seconds: $profileFailure" + } + } + } catch { $failures.Add("profile teardown: $($_.Exception.Message)") } + try { + if ($account) { Remove-LocalUser -Name $fixtureUser -ErrorAction Stop } } catch { $failures.Add("account teardown: $($_.Exception.Message)") } try { - $profile = Join-Path $env:SystemDrive "Users/$fixtureUser" - if ($ownsAccount -and (Test-Path -LiteralPath $profile)) { - Remove-Item -LiteralPath $profile -Recurse -Force -ErrorAction Stop + if ($ownsAccount -and (Test-Path -LiteralPath $profilePath)) { + Remove-Item -LiteralPath $profilePath -Recurse -Force -ErrorAction Stop } if ($ownsFixture -and (Test-Path -LiteralPath $fixture)) { Remove-Item -LiteralPath $fixture -Recurse -Force -ErrorAction Stop @@ -1681,6 +1733,9 @@ jobs: if ($ownsAccount -and (Get-LocalUser -Name $fixtureUser -ErrorAction SilentlyContinue)) { $failures.Add('fixture account remains registered') } + if ($ownsAccount -and (Test-Path -LiteralPath $profilePath)) { + $failures.Add('fixture profile directory remains') + } if ($failures.Count -gt 0) { throw ($failures -join '; ') } - name: Upload unpublished artifact evidence diff --git a/config/scripts/ssh-relay-runtime-workflow.test.mjs b/config/scripts/ssh-relay-runtime-workflow.test.mjs index f3ec13d255b..273c61b10d0 100644 --- a/config/scripts/ssh-relay-runtime-workflow.test.mjs +++ b/config/scripts/ssh-relay-runtime-workflow.test.mjs @@ -179,8 +179,10 @@ describe('SSH relay runtime artifact workflow', () => { expect(start.run).toContain("if ($nativeFile.Name -eq 'libcrypto.dll') { continue }") expect(start.run).toContain("$signature.Status -ne 'Valid'") expect(start.run).toContain('CN=Microsoft Corporation') - expect(start.run).toContain('[Security.Principal.WindowsIdentity]::GetCurrent().User.Value') - expect(start.run).toContain('& icacls.exe $Path /remove "*$creatorSid"') + expect(start.run).toContain('[Security.AccessControl.DirectorySecurity]::new()') + expect(start.run).toContain('[Security.AccessControl.FileSecurity]::new()') + expect(start.run).toContain('$security.SetAccessRuleProtection($true, $false)') + expect(start.run).toContain('[void]$security.AddAccessRule($accessRule)') expect(start.run).toContain('$acl.AreAccessRulesProtected') expect(start.run).toContain('Fixture ACL trustee closure changed') expect(start.run).toContain('@($systemSid, $administratorsSid)') @@ -215,6 +217,9 @@ describe('SSH relay runtime artifact workflow', () => { expect(stop.run).not.toContain('service-backup.json') expect(stop.run).not.toContain('sc.exe config') expect(stop.run).toContain('sc.exe delete') + expect(stop.run).toContain('Remove-CimInstance -InputObject $profile') + expect(stop.run).toContain('Fixture profile did not settle within 10 seconds') + expect(stop.run).toContain('fixture profile directory remains') expect(stop.run).toContain('Remove-LocalUser') }) diff --git a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md index 33674095589..3cb25f2833a 100644 --- a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md +++ b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist-summary.md @@ -9,8 +9,8 @@ keeps commands, hashes, runner identities, timings, and failure details. A checked box means the work has evidence in the detailed ledger. Design approval alone does not complete a box. -Active checkpoint: **Milestone 6 / Work Package 5 bounded runtime transfer — bounded official -Windows OpenSSH fixture correction, 2026-07-15, Codex implementation owner.** +Active checkpoint: **Milestone 6 / Work Package 5 bounded runtime transfer — exact-head Windows +OpenSSH live-proof diagnosis and fixture correction, 2026-07-15, Codex implementation owner.** `E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-PORTABLE-FIXTURE-LOCAL-001` records the portable-fixture correction locally green: the no-Orca-publication workflow oracle permits the one exact pinned Microsoft fixture release URL while Orca release URLs, `gh release`, and `contents: write` remain @@ -50,7 +50,24 @@ SID, assert the exact allowed SID closure, and rerun. correction locally green: protected allow-only ACLs remove the creator and require exact trustee SID closure; 10/10 focused workflow cases, all three PowerShell blocks, 283/283 artifact contracts, typecheck, full lint/reliability/max-lines, format, diff, and protected-resolver isolation pass. Push -for fresh exact-head x64/arm64 proof. +for fresh exact-head x64/arm64 proof. Exact head +`d28552f0d953e40d96a4c24cdcdb5ba7d85b7c9f` is pushed; artifact run `29541119819` is RED after +independently proving all archive/hash/closure/signer/build/cache gates. Windows x64 job +`87763404467` starts OpenSSH 10.0p2, proves PowerShell 5.1 and pinned-key authentication, then the +production system-SSH connection probe times out before transfer metrics; teardown also detects a +loaded fixture profile instead of silently leaving it. Windows ARM64 job `87763404492` stops earlier +at the exact ACL oracle because the C: fixture root retains explicit Authenticated Users and Users +ACEs in addition to the intended SYSTEM, Administrators, and fixture-user closure; ownership-safe +teardown passes. Diagnose and correct both fixture-exposed contracts, preserving connection reuse +and bounded deterministic teardown, then require fresh exact-head x64/arm64 proof. +`E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-EXACT-HEAD-CORRECTION-LOCAL-001` records the three bounded +corrections locally green: the no-input production probe closes native OpenSSH stdin, fixture DACLs +are atomically replaced with exact protected allow-only rules independent of drive defaults, and an +owned `Win32_UserProfile` is deleted through a bounded native lifecycle before account/filesystem +teardown. Focused tests pass 71 cases with one declared live skip, all three PowerShell blocks parse, +all 283 artifact contracts pass, typecheck and full lint/reliability/max-lines pass, formatting/diff +are clean, and the protected resolver files remain unchanged. Commit/push this isolated package and +require fresh x64/arm64 live proof before advancing. `E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-AUDIT-001` fixes the loopback-only official Microsoft server, fixture-owned non-admin account/ACL, exact host-key trust, Windows PowerShell 5.1, serial/default and four-channel metrics, cancellation/collision/cleanup, and deterministic teardown diff --git a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md index b381fe1b99d..16d1c3936a4 100644 --- a/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md +++ b/docs/reference/plans/2026-07-14-ssh-relay-github-release-implementation-checklist.md @@ -8,8 +8,8 @@ work; keep exact commands, runner identities, hashes, metrics, and residual gaps Date created: 2026-07-14
Last updated: 2026-07-15
-Current phase: Milestone 6 / Work Package 5 bounded runtime transfer — **In progress — 2026-07-15, Codex implementation owner: bounded official Windows OpenSSH fixture correction**. Keep every capability disconnected from every production/default SSH path. Real Apple/SignPath rehearsals remain an explicit late gate and are not on this package's critical path.
-Session checkpoint: **PORTABLE HOST-KEY ACL CORRECTION LOCALLY GREEN / EXACT-HEAD PROOF NEXT — 2026-07-15, Codex implementation owner** — exact pushed head `130d57d951b5e5cfb0e1e21183ce116684924e2b`, artifact run `29540409361`, and x64/arm64 jobs `87761260157`/`87761260166` pass checkout/toolchain, 283 contracts, clean-build equality/smoke, full-size cache, both pinned archive/library hashes, exact 15-file closure, and every audited common-name/thumbprint check before independently exposing the same narrower RED. Portable OpenSSH rejects the generated host key because `icacls /grant:r` leaves each key creator's explicit runner-account ACE; both service starts fail and both ownership-safe teardowns pass. `E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-PORTABLE-ACL-CORRECTION-LOCAL-001` removes the known creator SID from every protected fixture ACL, asserts protected/allow-only exact trustee SID closure before service start, and passes focused, PowerShell syntax, 283 artifact-contract, typecheck, lint/reliability/max-lines, format, diff, and isolation gates. Commit/push and obtain fresh x64/arm64 proof. Production runtime bytes still cross authenticated SSH and no product remote downloads anything. Do not connect a product caller, change legacy upload/fallback/default behavior, enable a tuple, publish an artifact, or perform SignPath work. Merging to `main` remains prohibited, SignPath is deferred, and legacy remains the production default.
+Current phase: Milestone 6 / Work Package 5 bounded runtime transfer — **In progress — 2026-07-15, Codex implementation owner: exact-head Windows OpenSSH live-proof diagnosis and fixture correction**. Keep every capability disconnected from every production/default SSH path. Real Apple/SignPath rehearsals remain an explicit late gate and are not on this package's critical path.
+Session checkpoint: **WINDOWS ACL/PROBE/PROFILE CORRECTION LOCALLY GREEN / EXACT-HEAD PROOF NEXT — 2026-07-15, Codex implementation owner** — exact pushed head `d28552f0d953e40d96a4c24cdcdb5ba7d85b7c9f`, artifact run `29541119819`, and x64/arm64 jobs `87763404467`/`87763404492` independently pass checkout/toolchain, 283 contracts, clean-build equality/smoke, full-size cache, both pinned archive/library hashes, exact 15-file closure, and every audited signer check before exposing three narrower gaps. ARM64's C: fixture root retains explicit Authenticated Users and Users ACEs; x64's no-input production `ssh.exe` probe leaves piped stdin open and waits 30 seconds after the remote command closes; x64 teardown finds the owned profile's loaded `UsrClass.dat`. `E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-EXACT-HEAD-CORRECTION-LOCAL-001` atomically replaces each fixture DACL with exact protected allow-only rules, closes stdin for the no-input production probe without disabling connection reuse, and deletes the owned `Win32_UserProfile` through a bounded native lifecycle before account/filesystem cleanup. Focused, PowerShell syntax, 283-contract, typecheck, full lint/reliability/max-lines, formatting, diff, and protected-resolver isolation gates pass locally. Commit/push and require fresh x64/arm64 live proof. Production runtime bytes still cross authenticated SSH and no product remote downloads anything. Do not connect a product caller, change legacy upload/fallback/default behavior, enable a tuple, publish an artifact, or perform SignPath work. Merging to `main` remains prohibited, SignPath is deferred, and legacy remains the production default.
Primary design: [SSH relay GitHub Release plan](./2026-07-14-ssh-relay-github-release-plan.html)
Motivating issues: [#8450](https://github.com/stablyai/orca/issues/8450), [#1693](https://github.com/stablyai/orca/issues/1693) @@ -19016,6 +19016,82 @@ config/vitest.config.ts --maxWorkers=1 --reporter=dot config/scripts/ssh-relay-r remains the sole production/default path; product/Beta/settings/fallback/tuple/publication/signing remain absent. +### E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-EXACT-HEAD-CI-RED-001 — native proof exposes ACL replacement, reuse-handshake, and profile-lifecycle gaps + +- Date/owner/source: 2026-07-15, Codex implementation owner; exact pushed head + `d28552f0d953e40d96a4c24cdcdb5ba7d85b7c9f`, artifact run `29541119819`, native x64 job + `87763404467` (`windows-2022`, Windows Server 2022 build 20348) and native ARM64 job + `87763404492` (`windows-11-arm`). +- Shared green boundary: both jobs pass exact checkout/toolchain, all 283 runtime artifact contracts, + clean-build equality/smoke, full-size client extraction/cache measurement, both exact portable + archive and `libcrypto.dll` hashes, exact 15-file closure, and every audited Authenticode common + name/thumbprint check. This preserves all earlier build/trust evidence. +- ARM64 RED: fixture download and extraction pass, then the pre-service exact ACL oracle rejects the + fixture root. Its protected allow-only trustee set is `S-1-5-11`, `S-1-5-18`, fixture user + `S-1-5-21-1882319117-3219095328-2125279949-1001`, `S-1-5-32-544`, and `S-1-5-32-545`. + Authenticated Users and Users are undeclared explicit entries inherited from that runner volume's + creation defaults; removing only the creator SID cannot establish exact closure. The service and + transfer never start, and the ownership-safe teardown passes. +- X64 RED: the same ACL oracle passes, the official server starts as + `OpenSSH_for_Windows_10.0p2 Win32-OpenSSH-GitHub, LibreSSL 4.2.0`, Windows PowerShell is + `5.1.20348.4294`, exact host-key trust and fixture-user public-key authentication pass. The + full-size suite then fails after roughly 30 seconds at `src/main/ssh/ssh-connection.ts:750` with + `System SSH connection timed out`, before serial transfer metrics. Server logs show an + authenticated session closing/read error plus a later pre-auth reset. Preserve + `systemSshConnectionReuse: true`; connection reuse is a required contract, not a fixture option to + disable. +- X64 teardown RED: service/account cleanup runs, but direct recursive deletion rejects + `C:\Users\orca_ssh_fixture\AppData\Local\Microsoft\Windows\UsrClass.dat` because the profile is + still loaded. This is a deterministic fixture lifecycle gap. Do not ignore the locked profile or + weaken teardown; unload/remove it through a bounded native profile lifecycle and retain exact + absence assertions. +- Correction gate: replace each fixture DACL with the exact declared allow-only SID set independent + of volume defaults; isolate and prove the production system-SSH reuse handshake against Windows + OpenSSH; unload/remove the owned fixture profile boundedly before filesystem deletion. Update the + workflow oracle, parse all PowerShell blocks, rerun focused and 283-contract/static/isolation + gates, then require fresh exact-head x64/arm64 service/auth/full-size/cancellation/collision/reuse/ + teardown proof. Legacy remains the sole production/default path and SignPath remains deferred. + +### E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-EXACT-HEAD-CORRECTION-LOCAL-001 — exact DACL, no-input probe, and owned-profile lifecycle are locally green + +- Date/owner/base/runner: 2026-07-15, Codex implementation owner; uncommitted correction atop exact + RED head `d28552f0d953e40d96a4c24cdcdb5ba7d85b7c9f` on Apple arm64, macOS 26.2 build 25C56, + Node 26.5.0, pnpm 10.24.0. Native Windows execution remains required. +- Correction: the production system-SSH connection probe now closes its no-input child stdin after + installing listeners. This lets native Windows `ssh.exe` settle after the remote echo closes and + changes no authentication, command, RPC, retry, fallback, or mux policy. The live fixture creates + a fresh `DirectorySecurity` or `FileSecurity` DACL and atomically installs only the declared + SYSTEM, Administrators, and optional fixture-user allow rules, so C:/D: defaults cannot survive. + Teardown uses the owned account SID to retry `Remove-CimInstance` for `Win32_UserProfile` for at + most ten seconds before account and directory deletion, retaining exact absence assertions. +- Focused command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts +--maxWorkers=1 src/main/ssh/ssh-connection.test.ts +src/main/ssh/ssh-relay-runtime-windows-system-ssh-openssh-full-size.test.ts +config/scripts/ssh-relay-runtime-workflow.test.mjs` passes 71/71 cases with the one declared native + live skip in 3.83 seconds real; maximum RSS is 162,643,968 bytes and peak footprint is 98,490,224 + bytes. Independent YAML extraction plus PowerShell `[scriptblock]::Create()` parses all three + start/measure/stop blocks. +- Release-contract command: `/usr/bin/time -l pnpm exec vitest run --config +config/vitest.config.ts --maxWorkers=1 --reporter=dot +config/scripts/ssh-relay-runtime-*.test.mjs` passes 50 files and 283/283 cases in 83.86 seconds + real; maximum RSS is 195,739,648 bytes and peak footprint is 97,408,952 bytes. +- Static evidence: `/usr/bin/time -l pnpm typecheck` passes in 30.12 seconds real; maximum RSS is + 1,253,359,616 bytes and peak footprint is 97,130,256 bytes. The first full-lint attempt honestly + rejected the workflow oracle at 601 lines; one redundant `Get-CimInstance` string assertion was + removed while the `Remove-CimInstance`, bounded timeout, exact profile-directory, and runtime + behavior assertions remain. The rerun `/usr/bin/time -l pnpm lint` passes in 73.17 seconds real + with only the 26 pre-existing warnings, all 41 reliability gates, the 355-entry max-lines ratchet, + localization, and bundled-guide checks; maximum RSS is 2,072,281,088 bytes and peak footprint is + 98,244,440 bytes. +- Isolation evidence: targeted `oxfmt --check`, `git diff --check`, and exact protected-file equality + against the base head pass. Only the artifact workflow/oracle, system-SSH connection/probe test, + and three planning artifacts are modified; the protected resolver files remain unchanged. +- Residual boundary: local macOS proof cannot execute Windows DACL replacement, native `ssh.exe` + stdin settlement, `Win32_UserProfile` deletion, service/auth, PowerShell 5.1, full-size transfer, + cancellation, collision, or teardown. Commit/push this isolated correction and require fresh + exact-head x64/arm64 proof before closing the live Windows gate. Legacy remains the sole + production/default path; product/Beta/settings/fallback/tuple/publication/signing remain absent. + ## Accepted Gaps No product gap is accepted merely because it appears in this list. Each entry requires explicit @@ -19074,12 +19150,13 @@ The project is not complete until every applicable item below is checked with ev ## Next Required Action -Commit and push the locally green role-based fixture trust correction in -`E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-LIBCRYPTO-CORRECTION-LOCAL-001`, then require fresh exact-head -x64/arm64 archive/native-closure/signature, ACL, PowerShell 5.1, exact full-size serial/four-channel/ -cancellation, collision, connection-reuse, and teardown evidence. Do not check the broad Milestone -6 Windows system-SSH item yet. Do not add an Electron/startup/product importer, per-target mode -wiring, fallback, tuple enablement, release publication, or default behavior. +Commit and push the locally green isolated correction in +`E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-EXACT-HEAD-CORRECTION-LOCAL-001`, then require fresh exact-head +x64/arm64 archive/native-closure/signature, ACL, PowerShell 5.1, exact +full-size serial/four-channel/cancellation, collision, connection-reuse, and teardown evidence. Do +not check the broad Milestone 6 Windows system-SSH item yet. Do not add an Electron/startup/product +importer, per-target mode wiring, fallback, tuple enablement, release publication, or default +behavior. Keep Node upstream `.tar.xz` inputs, Windows ZIP, `ORCA_RELAY_PATH`, existing desktop required-assets behavior, detached-signature byte encoding, Windows arm64 build 26100, macOS 13.5, Linux kernel 4.18, release-cut, desktop builds, publication, and every tuple separately gated. diff --git a/docs/reference/plans/2026-07-14-ssh-relay-github-release-plan.html b/docs/reference/plans/2026-07-14-ssh-relay-github-release-plan.html index 7bcff37f4e0..39e6db19cf1 100644 --- a/docs/reference/plans/2026-07-14-ssh-relay-github-release-plan.html +++ b/docs/reference/plans/2026-07-14-ssh-relay-github-release-plan.html @@ -1642,7 +1642,8 @@ Full-size runtime over SFTP and POSIX/Windows system SSH; tar/no-tar; no remote hash tools; corrupted upload; bundled-Node corruption/failure; bounded memory - and concurrency; MaxSessions=1; interrupted upload; stale lock + and concurrency; no-input system-SSH probe stdin/child settlement; + MaxSessions=1; interrupted upload; stale lock Bundled Node hashes the full staged tree before sentinel write; @@ -1666,9 +1667,11 @@ This trust exception provisions only the loopback CI server. Production clients and SSH hosts perform no fixture download. Fixture host-key and authorized-key - ACL creation removes creator-specific access and asserts the exact allowed SID - closure before service start; none of this weakens runtime artifact, - native-signing, transfer, install, or launch trust. + ACL creation replaces volume-specific defaults with the exact allowed, + protected, allow-only SID closure before service start. Owned fixture profiles + are unloaded and removed through a bounded native lifecycle before exact + teardown assertions; none of this weakens runtime artifact, native-signing, + transfer, install, or launch trust. diff --git a/src/main/ssh/ssh-connection.test.ts b/src/main/ssh/ssh-connection.test.ts index 90807615000..b0c9194d992 100644 --- a/src/main/ssh/ssh-connection.test.ts +++ b/src/main/ssh/ssh-connection.test.ts @@ -932,6 +932,11 @@ describe('SshConnection', () => { it('uses system SSH transport when ProxyUseFdpass is resolved by OpenSSH', async () => { vi.mocked(resolveWithSshG).mockResolvedValueOnce(createResolvedConfig()) + let probeChannel: ReturnType | undefined + spawnSystemSshCommandMock.mockImplementationOnce(() => { + probeChannel = createSystemCommandChannel() + return probeChannel + }) const conn = new SshConnection(createTarget({ configHost: 'fdpass-host' }), createCallbacks()) await conn.connect() @@ -947,6 +952,7 @@ describe('SshConnection', () => { resolvedConfig: expect.objectContaining({ proxyUseFdpass: true }) } ) + expect(probeChannel?.stdin.end).toHaveBeenCalledOnce() }) it('allows concurrent exec commands for system SSH with an Orca ControlMaster socket', async () => { diff --git a/src/main/ssh/ssh-connection.ts b/src/main/ssh/ssh-connection.ts index a1e55027080..a8f11275c0b 100644 --- a/src/main/ssh/ssh-connection.ts +++ b/src/main/ssh/ssh-connection.ts @@ -755,6 +755,9 @@ export class SshConnection { channel.stderr.on('data', onStderrData) channel.on('error', onError) channel.on('close', onClose) + // Why: native Windows OpenSSH keeps its input worker alive while the + // pipe is open, even though this probe can never consume stdin. + ;(channel as ClientChannel & { stdin: NodeJS.WritableStream }).stdin.end() }) } catch (err) { this.useSystemSshTransport = false