From aee82d2058d94d3a46862207f303f41ae8b95370 Mon Sep 17 00:00:00 2001 From: Vadim Smirnov Date: Wed, 12 Aug 2026 11:30:23 +0200 Subject: [PATCH 1/5] ci: let winget orchestrate SDK installation --- README.md | 2 +- scripts/Invoke-HostedSdkExperiment.ps1 | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b7538fd..8f6bb1b 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Configure `WIRESOCKUI_WGBOOSTER_PATH_X86`, `WIRESOCKUI_WGBOOSTER_PATH_X64`, and Install a dedicated organization GitHub App with only Self-hosted runners (read), expose its `WIRESOCK_SDK_RUNNER_POLICY_CLIENT_ID` variable and `WIRESOCK_SDK_RUNNER_POLICY_PRIVATE_KEY` secret through the protected `wiresock-sdk` environment, and scope its installation to this repository. The hosted preflight uses its short-lived token only to audit the runner group; candidate code never receives that token. -The manually dispatched **Hosted WireSock SDK experiment** is an isolated x64 feasibility check and does not replace the protected self-hosted runner policy. It runs only from the current protected `main` tip on a disposable GitHub-hosted Windows VM, bootstraps WinGet when necessary, downloads the exact `NTKERNEL.WireSockVPNClientCLI` SDK version, verifies the installer against the audited WinGet SHA-256 and Authenticode signature before execution, builds and installation-tests an unsigned candidate MSI, and exercises the real SDK lifecycle with synthetic profiles restricted to IANA documentation networks. The experiment uses no VPN credentials or repository secrets and uninstalls the SDK before the VM is discarded. Run it from **Actions → Hosted WireSock SDK experiment → Run workflow**; promote this design to x86/ARM64 or release gating only after the x64 driver, routing, and cleanup behavior succeeds consistently. +The manually dispatched **Hosted WireSock SDK experiment** is an isolated x64 feasibility check and does not replace the protected self-hosted runner policy. It runs only from the current protected `main` tip on a disposable GitHub-hosted Windows VM, bootstraps WinGet when necessary, preflights the exact `NTKERNEL.WireSockVPNClientCLI` SDK version against the audited WinGet SHA-256 and Authenticode signature, installs that pinned package through WinGet, builds and installation-tests an unsigned candidate MSI, and exercises the real SDK lifecycle with synthetic profiles restricted to IANA documentation networks. The experiment uses no VPN credentials or repository secrets and uninstalls the SDK before the VM is discarded. Run it from **Actions → Hosted WireSock SDK experiment → Run workflow**; promote this design to x86/ARM64 or release gating only after the x64 driver, routing, and cleanup behavior succeeds consistently. Native state and statistics polling use bounded asynchronous queries. If `wgbooster.dll` does not return before the query timeout, WireSockUI stops issuing additional native operations, records a recovery marker, and requires recovery or restart. Startup also compares the process and `wgbooster.dll` PE architectures so x86/x64/ARM64 mismatches are reported directly. diff --git a/scripts/Invoke-HostedSdkExperiment.ps1 b/scripts/Invoke-HostedSdkExperiment.ps1 index 51d79a8..4ee7b6e 100644 --- a/scripts/Invoke-HostedSdkExperiment.ps1 +++ b/scripts/Invoke-HostedSdkExperiment.ps1 @@ -377,7 +377,16 @@ try { throw "SDK installer has Authenticode status '$($installerSignature.Status)'." } - & $installers[0].FullName /S /NCRC + & $wingetPath install ` + --id $packageId ` + --exact ` + --version $packageVersion ` + --architecture x64 ` + --source winget ` + --silent ` + --accept-package-agreements ` + --accept-source-agreements ` + --disable-interactivity Assert-LastExitCode -Operation "Installing $packageId $packageVersion" $installedSdk = $true From b36da580ff2e68cf4bd0aa74edc3196351fa4c77 Mon Sep 17 00:00:00 2001 From: Vadim Smirnov Date: Wed, 12 Aug 2026 11:57:12 +0200 Subject: [PATCH 2/5] ci: wait for validated SDK installer tree --- README.md | 2 +- scripts/Invoke-HostedSdkExperiment.ps1 | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8f6bb1b..9ae4914 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Configure `WIRESOCKUI_WGBOOSTER_PATH_X86`, `WIRESOCKUI_WGBOOSTER_PATH_X64`, and Install a dedicated organization GitHub App with only Self-hosted runners (read), expose its `WIRESOCK_SDK_RUNNER_POLICY_CLIENT_ID` variable and `WIRESOCK_SDK_RUNNER_POLICY_PRIVATE_KEY` secret through the protected `wiresock-sdk` environment, and scope its installation to this repository. The hosted preflight uses its short-lived token only to audit the runner group; candidate code never receives that token. -The manually dispatched **Hosted WireSock SDK experiment** is an isolated x64 feasibility check and does not replace the protected self-hosted runner policy. It runs only from the current protected `main` tip on a disposable GitHub-hosted Windows VM, bootstraps WinGet when necessary, preflights the exact `NTKERNEL.WireSockVPNClientCLI` SDK version against the audited WinGet SHA-256 and Authenticode signature, installs that pinned package through WinGet, builds and installation-tests an unsigned candidate MSI, and exercises the real SDK lifecycle with synthetic profiles restricted to IANA documentation networks. The experiment uses no VPN credentials or repository secrets and uninstalls the SDK before the VM is discarded. Run it from **Actions → Hosted WireSock SDK experiment → Run workflow**; promote this design to x86/ARM64 or release gating only after the x64 driver, routing, and cleanup behavior succeeds consistently. +The manually dispatched **Hosted WireSock SDK experiment** is an isolated x64 feasibility check and does not replace the protected self-hosted runner policy. It runs only from the current protected `main` tip on a disposable GitHub-hosted Windows VM, bootstraps WinGet when necessary, downloads the exact `NTKERNEL.WireSockVPNClientCLI` SDK version, verifies the installer against the audited WinGet SHA-256 and Authenticode signature, waits for that validated installer's full process tree, builds and installation-tests an unsigned candidate MSI, and exercises the real SDK lifecycle with synthetic profiles restricted to IANA documentation networks. The experiment uses no VPN credentials or repository secrets and uninstalls the SDK before the VM is discarded. Run it from **Actions → Hosted WireSock SDK experiment → Run workflow**; promote this design to x86/ARM64 or release gating only after the x64 driver, routing, and cleanup behavior succeeds consistently. Native state and statistics polling use bounded asynchronous queries. If `wgbooster.dll` does not return before the query timeout, WireSockUI stops issuing additional native operations, records a recovery marker, and requires recovery or restart. Startup also compares the process and `wgbooster.dll` PE architectures so x86/x64/ARM64 mismatches are reported directly. diff --git a/scripts/Invoke-HostedSdkExperiment.ps1 b/scripts/Invoke-HostedSdkExperiment.ps1 index 4ee7b6e..d55d2d2 100644 --- a/scripts/Invoke-HostedSdkExperiment.ps1 +++ b/scripts/Invoke-HostedSdkExperiment.ps1 @@ -377,18 +377,17 @@ try { throw "SDK installer has Authenticode status '$($installerSignature.Status)'." } - & $wingetPath install ` - --id $packageId ` - --exact ` - --version $packageVersion ` - --architecture x64 ` - --source winget ` - --silent ` - --accept-package-agreements ` - --accept-source-agreements ` - --disable-interactivity - Assert-LastExitCode -Operation "Installing $packageId $packageVersion" $installedSdk = $true + $installerProcess = Start-Process ` + -FilePath $installers[0].FullName ` + -ArgumentList @('/S', '/NCRC') ` + -Wait ` + -PassThru + if ($installerProcess.ExitCode -ne 0) { + throw ( + "Installing $packageId $packageVersion failed with exit code " + + "$($installerProcess.ExitCode).") + } $libraries = @(Get-WireSockSdkLibraries) if ($libraries.Count -eq 0) { From cb3d3b361076e17092bbc5137aba4dc00c0ad06e Mon Sep 17 00:00:00 2001 From: Vadim Smirnov Date: Wed, 12 Aug 2026 12:19:53 +0200 Subject: [PATCH 3/5] ci: mark SDK installed after successful exit --- scripts/Invoke-HostedSdkExperiment.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Invoke-HostedSdkExperiment.ps1 b/scripts/Invoke-HostedSdkExperiment.ps1 index d55d2d2..7000848 100644 --- a/scripts/Invoke-HostedSdkExperiment.ps1 +++ b/scripts/Invoke-HostedSdkExperiment.ps1 @@ -377,7 +377,6 @@ try { throw "SDK installer has Authenticode status '$($installerSignature.Status)'." } - $installedSdk = $true $installerProcess = Start-Process ` -FilePath $installers[0].FullName ` -ArgumentList @('/S', '/NCRC') ` @@ -388,6 +387,7 @@ try { "Installing $packageId $packageVersion failed with exit code " + "$($installerProcess.ExitCode).") } + $installedSdk = $true $libraries = @(Get-WireSockSdkLibraries) if ($libraries.Count -eq 0) { From d15cf05dc36fe921e788179e75f8257352007536 Mon Sep 17 00:00:00 2001 From: Vadim Smirnov Date: Wed, 12 Aug 2026 12:28:42 +0200 Subject: [PATCH 4/5] ci: wait for SDK registration readiness --- README.md | 2 +- scripts/Invoke-HostedSdkExperiment.ps1 | 36 +++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9ae4914..06c516f 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Configure `WIRESOCKUI_WGBOOSTER_PATH_X86`, `WIRESOCKUI_WGBOOSTER_PATH_X64`, and Install a dedicated organization GitHub App with only Self-hosted runners (read), expose its `WIRESOCK_SDK_RUNNER_POLICY_CLIENT_ID` variable and `WIRESOCK_SDK_RUNNER_POLICY_PRIVATE_KEY` secret through the protected `wiresock-sdk` environment, and scope its installation to this repository. The hosted preflight uses its short-lived token only to audit the runner group; candidate code never receives that token. -The manually dispatched **Hosted WireSock SDK experiment** is an isolated x64 feasibility check and does not replace the protected self-hosted runner policy. It runs only from the current protected `main` tip on a disposable GitHub-hosted Windows VM, bootstraps WinGet when necessary, downloads the exact `NTKERNEL.WireSockVPNClientCLI` SDK version, verifies the installer against the audited WinGet SHA-256 and Authenticode signature, waits for that validated installer's full process tree, builds and installation-tests an unsigned candidate MSI, and exercises the real SDK lifecycle with synthetic profiles restricted to IANA documentation networks. The experiment uses no VPN credentials or repository secrets and uninstalls the SDK before the VM is discarded. Run it from **Actions → Hosted WireSock SDK experiment → Run workflow**; promote this design to x86/ARM64 or release gating only after the x64 driver, routing, and cleanup behavior succeeds consistently. +The manually dispatched **Hosted WireSock SDK experiment** is an isolated x64 feasibility check and does not replace the protected self-hosted runner policy. It runs only from the current protected `main` tip on a disposable GitHub-hosted Windows VM, bootstraps WinGet when necessary, downloads the exact `NTKERNEL.WireSockVPNClientCLI` SDK version, verifies the installer against the audited WinGet SHA-256 and Authenticode signature, waits for installation completion and the expected signed SDK artifacts, builds and installation-tests an unsigned candidate MSI, and exercises the real SDK lifecycle with synthetic profiles restricted to IANA documentation networks. The experiment uses no VPN credentials or repository secrets and uninstalls the SDK before the VM is discarded. Run it from **Actions → Hosted WireSock SDK experiment → Run workflow**; promote this design to x86/ARM64 or release gating only after the x64 driver, routing, and cleanup behavior succeeds consistently. Native state and statistics polling use bounded asynchronous queries. If `wgbooster.dll` does not return before the query timeout, WireSockUI stops issuing additional native operations, records a recovery marker, and requires recovery or restart. Startup also compares the process and `wgbooster.dll` PE architectures so x86/x64/ARM64 mismatches are reported directly. diff --git a/scripts/Invoke-HostedSdkExperiment.ps1 b/scripts/Invoke-HostedSdkExperiment.ps1 index 7000848..301148f 100644 --- a/scripts/Invoke-HostedSdkExperiment.ps1 +++ b/scripts/Invoke-HostedSdkExperiment.ps1 @@ -172,6 +172,37 @@ function Get-WireSockSdkLibraries { return @($libraries) } +function Wait-WireSockSdkLibraries { + param( + [ValidateRange(1, 600)] + [int] $TimeoutSeconds = 120, + + [ValidateRange(100, 10000)] + [int] $PollIntervalMilliseconds = 2000 + ) + + $stopwatch = [Diagnostics.Stopwatch]::StartNew() + try { + do { + $libraries = @(Get-WireSockSdkLibraries) + if ($libraries.Count -gt 0) { + return $libraries + } + if ($stopwatch.Elapsed.TotalSeconds -ge $TimeoutSeconds) { + break + } + Start-Sleep -Milliseconds $PollIntervalMilliseconds + } while ($true) + } + finally { + $stopwatch.Stop() + } + + throw ( + "The SDK installer registered no wgbooster.dll candidate within " + + "$TimeoutSeconds seconds.") +} + function Set-ProtectedProfileAcl { param( [Parameter(Mandatory = $true)] @@ -389,10 +420,7 @@ try { } $installedSdk = $true - $libraries = @(Get-WireSockSdkLibraries) - if ($libraries.Count -eq 0) { - throw 'The SDK installer registered no wgbooster.dll candidate.' - } + $libraries = @(Wait-WireSockSdkLibraries) $libraryPath = $null foreach ($candidate in $libraries) { $signature = Get-AuthenticodeSignature -FilePath $candidate From bc3ead273e88f1cd350acf3fc250b533c0942f75 Mon Sep 17 00:00:00 2001 From: Vadim Smirnov Date: Wed, 12 Aug 2026 12:35:39 +0200 Subject: [PATCH 5/5] ci: harden SDK readiness diagnostics --- scripts/Invoke-HostedSdkExperiment.ps1 | 61 +++++++++++++++++++------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/scripts/Invoke-HostedSdkExperiment.ps1 b/scripts/Invoke-HostedSdkExperiment.ps1 index 301148f..1513f6b 100644 --- a/scripts/Invoke-HostedSdkExperiment.ps1 +++ b/scripts/Invoke-HostedSdkExperiment.ps1 @@ -36,6 +36,11 @@ $buildMsiScriptPath = Join-Path $PSScriptRoot 'Build-Msi.ps1' $testMsiInstallationScriptPath = Join-Path ` $PSScriptRoot ` 'Test-MsiInstallation.ps1' +$wireSockSdkRegistryPaths = @( + 'SOFTWARE\WireSock Foundation\WireSock Secure Connect', + 'SOFTWARE\WireSock Foundation\WireSock Secure Connect Pro', + 'SOFTWARE\NTKernelResources\WinpkFilterForVPNClient' +) function Assert-LastExitCode { param( @@ -48,6 +53,27 @@ function Assert-LastExitCode { } } +function Assert-SdkInstallerExitCode { + param( + [Parameter(Mandatory = $true)] + [int] $ExitCode, + + [Parameter(Mandatory = $true)] + [string] $Operation + ) + + if ($ExitCode -eq 0) { + return + } + if ($ExitCode -eq 3010) { + Write-Warning ( + "$Operation succeeded but requested a restart; continuing on " + + 'the disposable hosted runner.') + return + } + throw "$Operation failed with exit code $ExitCode." +} + function Remove-HostedExperimentDirectory { param( [Parameter(Mandatory = $true)] @@ -115,11 +141,6 @@ function Get-WinGetExecutable { } function Get-WireSockSdkLibraries { - $registryPaths = @( - 'SOFTWARE\WireSock Foundation\WireSock Secure Connect', - 'SOFTWARE\WireSock Foundation\WireSock Secure Connect Pro', - 'SOFTWARE\NTKernelResources\WinpkFilterForVPNClient' - ) $registryViews = @( [Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32 @@ -130,7 +151,7 @@ function Get-WireSockSdkLibraries { [Microsoft.Win32.RegistryHive]::LocalMachine, $view) try { - foreach ($registryPath in $registryPaths) { + foreach ($registryPath in $wireSockSdkRegistryPaths) { $key = $baseKey.OpenSubKey($registryPath) try { $location = if ($null -eq $key) { @@ -181,18 +202,22 @@ function Wait-WireSockSdkLibraries { [int] $PollIntervalMilliseconds = 2000 ) + $timeoutMilliseconds = $TimeoutSeconds * 1000 $stopwatch = [Diagnostics.Stopwatch]::StartNew() try { - do { + while ($stopwatch.ElapsedMilliseconds -lt $timeoutMilliseconds) { $libraries = @(Get-WireSockSdkLibraries) if ($libraries.Count -gt 0) { return $libraries } - if ($stopwatch.Elapsed.TotalSeconds -ge $TimeoutSeconds) { - break + $remainingMilliseconds = + $timeoutMilliseconds - $stopwatch.ElapsedMilliseconds + if ($remainingMilliseconds -gt 0) { + Start-Sleep -Milliseconds ([Math]::Min( + $PollIntervalMilliseconds, + [int]$remainingMilliseconds)) } - Start-Sleep -Milliseconds $PollIntervalMilliseconds - } while ($true) + } } finally { $stopwatch.Stop() @@ -200,7 +225,11 @@ function Wait-WireSockSdkLibraries { throw ( "The SDK installer registered no wgbooster.dll candidate within " + - "$TimeoutSeconds seconds.") + "$TimeoutSeconds seconds while polling every " + + "$PollIntervalMilliseconds milliseconds. Expected InstallLocation " + + 'under the 32-bit or 64-bit HKLM registry paths ' + + "'$($wireSockSdkRegistryPaths -join "', '")', with wgbooster.dll " + + 'in that location, sdk, or bin.') } function Set-ProtectedProfileAcl { @@ -413,11 +442,9 @@ try { -ArgumentList @('/S', '/NCRC') ` -Wait ` -PassThru - if ($installerProcess.ExitCode -ne 0) { - throw ( - "Installing $packageId $packageVersion failed with exit code " + - "$($installerProcess.ExitCode).") - } + Assert-SdkInstallerExitCode ` + -ExitCode $installerProcess.ExitCode ` + -Operation "Installing $packageId $packageVersion" $installedSdk = $true $libraries = @(Wait-WireSockSdkLibraries)