diff --git a/README.md b/README.md index b7538fd..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 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, 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 51d79a8..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) { @@ -172,6 +193,45 @@ function Get-WireSockSdkLibraries { return @($libraries) } +function Wait-WireSockSdkLibraries { + param( + [ValidateRange(1, 600)] + [int] $TimeoutSeconds = 120, + + [ValidateRange(100, 10000)] + [int] $PollIntervalMilliseconds = 2000 + ) + + $timeoutMilliseconds = $TimeoutSeconds * 1000 + $stopwatch = [Diagnostics.Stopwatch]::StartNew() + try { + while ($stopwatch.ElapsedMilliseconds -lt $timeoutMilliseconds) { + $libraries = @(Get-WireSockSdkLibraries) + if ($libraries.Count -gt 0) { + return $libraries + } + $remainingMilliseconds = + $timeoutMilliseconds - $stopwatch.ElapsedMilliseconds + if ($remainingMilliseconds -gt 0) { + Start-Sleep -Milliseconds ([Math]::Min( + $PollIntervalMilliseconds, + [int]$remainingMilliseconds)) + } + } + } + finally { + $stopwatch.Stop() + } + + throw ( + "The SDK installer registered no wgbooster.dll candidate within " + + "$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 { param( [Parameter(Mandatory = $true)] @@ -377,14 +437,17 @@ try { throw "SDK installer has Authenticode status '$($installerSignature.Status)'." } - & $installers[0].FullName /S /NCRC - Assert-LastExitCode -Operation "Installing $packageId $packageVersion" + $installerProcess = Start-Process ` + -FilePath $installers[0].FullName ` + -ArgumentList @('/S', '/NCRC') ` + -Wait ` + -PassThru + Assert-SdkInstallerExitCode ` + -ExitCode $installerProcess.ExitCode ` + -Operation "Installing $packageId $packageVersion" $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