mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-13 04:31:04 -10:00
* fix(release): sign Windows payload executables Problem: Windows Defender can quarantine reasonix-desktop.exe after install or update even though the downloaded NSIS installer is signed. Root cause: The release workflow applied Authenticode only to the finished NSIS container. The desktop executable, support binaries, portable archive entries, and generated uninstaller remained unsigned after extraction. Fix: Sign the exact Windows payload through a versioned SignPath configuration, rebuild the portable archive and NSIS installer from those signed files, verify the payload before signing the outer installer, and fail official releases closed when signing is unavailable. Preserve and inject a signed NSIS uninstaller in the second packaging pass. Verification: - go test ./... (desktop module) - env -u DEEPSEEK_API_KEY go test ./... - bash scripts/release-workflows.test.sh - ./scripts/cache-guard.sh - actionlint release workflows - bash syntax and git diff checks * fix(release): separate canary installer signing config * fix(release): automate gated SignPath approvals Problem: Release certificates require one SignPath approval per request, causing four extra manual approval actions after the GitHub release gate. Root cause: The desktop workflow submitted each request synchronously and waited for an interactive SignPath approver even though the same release authority had already approved the protected GitHub environment. Fix: Submit requests asynchronously, validate the expected signing policy, approve them through the dedicated CI identity, poll to a final successful state, and securely download the signed artifacts. Exercise the same path in canary with an approval-enabled test policy and document the administrator contract. Verification: - cd desktop && go test ./... - bash scripts/release-workflows.test.sh - actionlint release-stable.yml release-desktop.yml - git diff --check * fix(release): default SignPath API endpoint The GitHub connector does not expose a SignPath API URL when requests are submitted asynchronously. Use SignPath's documented SaaS endpoint by default and keep the script override available for self-hosted installations. Verified with desktop Go tests, release workflow contract tests, actionlint, and git diff --check. * fix(release): restrict production signing origin Problem: Repository-level SignPath credentials were available to workflows on ordinary repository branches, while the production signing policy allowed every branch origin. A writer could therefore construct an unreviewed workflow that requested a trusted production signature. Root cause: Stable and prerelease builds ran directly from tag origins, so the SignPath policy could not use one exact protected branch without breaking tag releases. The policy had consequently been configured with an unrestricted branch wildcard. Fix: Relay stable and desktop prerelease tags to the current workflow on protected main-v2, keep immutable tags as build candidates, add a gated non-publishing production-signing smoke mode, and codify the exact main-v2 SignPath origin in tests and administrator documentation. Verification: - bash scripts/release-workflows.test.sh - actionlint v1.7.12 on all changed workflows - cd desktop && go test ./... - cd desktop && go test . -run signing contract tests - ./scripts/cache-guard.sh - git diff --check * fix(release): wait for manual smoke approval Problem: The first production-certificate smoke run failed immediately after submitting its two payload requests when the CI identity had intentionally been removed from the production approver list. Root cause: The request completion helper always called the SignPath Approve API as soon as a request entered WaitingForApproval, even in the explicitly human-reviewed smoke mode. Fix: Add an external-approval mode that polls without approving, enable it only for production signing smoke runs, and document and test the manual verification boundary. Normal canary and release automation retains CI approval. Verification: - bash scripts/release-workflows.test.sh - actionlint v1.7.12 on release-desktop.yml - cd desktop && go test . -run signing contract tests - git diff --check
162 lines
4.9 KiB
PowerShell
162 lines
4.9 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidatePattern("^[0-9a-fA-F-]{36}$")]
|
|
[string]$OrganizationId,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidatePattern("^[0-9a-fA-F-]{36}$")]
|
|
[string]$SigningRequestId,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string]$ExpectedSigningPolicySlug,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[string]$OutputArtifactDirectory,
|
|
|
|
[ValidateNotNullOrEmpty()]
|
|
[string]$ApiUrl = "https://app.signpath.io/api",
|
|
|
|
[ValidateRange(30, 7200)]
|
|
[int]$TimeoutSeconds = 1800,
|
|
|
|
[ValidateRange(1, 60)]
|
|
[int]$PollIntervalSeconds = 5,
|
|
|
|
[switch]$WaitForExternalApproval
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
$ProgressPreference = "SilentlyContinue"
|
|
|
|
$apiToken = $env:SIGNPATH_API_TOKEN
|
|
if ([string]::IsNullOrWhiteSpace($apiToken)) {
|
|
throw "SIGNPATH_API_TOKEN is required to approve and download a SignPath request."
|
|
}
|
|
|
|
$parsedApiUrl = $null
|
|
if (
|
|
-not [Uri]::TryCreate($ApiUrl, [UriKind]::Absolute, [ref]$parsedApiUrl) -or
|
|
$parsedApiUrl.Scheme -ne "https"
|
|
) {
|
|
throw "ApiUrl must be an absolute HTTPS URL."
|
|
}
|
|
|
|
$workspace = if ([string]::IsNullOrWhiteSpace($env:GITHUB_WORKSPACE)) {
|
|
[IO.Path]::GetFullPath((Get-Location).Path)
|
|
} else {
|
|
[IO.Path]::GetFullPath($env:GITHUB_WORKSPACE)
|
|
}
|
|
$outputDirectory = if ([IO.Path]::IsPathRooted($OutputArtifactDirectory)) {
|
|
[IO.Path]::GetFullPath($OutputArtifactDirectory)
|
|
} else {
|
|
[IO.Path]::GetFullPath((Join-Path $workspace $OutputArtifactDirectory))
|
|
}
|
|
$workspacePrefix = $workspace.TrimEnd(
|
|
[IO.Path]::DirectorySeparatorChar,
|
|
[IO.Path]::AltDirectorySeparatorChar
|
|
) + [IO.Path]::DirectorySeparatorChar
|
|
if (-not $outputDirectory.StartsWith($workspacePrefix, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "OutputArtifactDirectory must resolve inside GITHUB_WORKSPACE."
|
|
}
|
|
|
|
$headers = @{
|
|
Authorization = "Bearer $apiToken"
|
|
}
|
|
$requestBaseUrl = (
|
|
$ApiUrl.TrimEnd("/") +
|
|
"/v1/$OrganizationId/SigningRequests/$SigningRequestId"
|
|
)
|
|
|
|
$request = Invoke-RestMethod `
|
|
-Method Get `
|
|
-Uri $requestBaseUrl `
|
|
-Headers $headers
|
|
if ($request.signingPolicySlug -ne $ExpectedSigningPolicySlug) {
|
|
throw (
|
|
"SignPath request policy mismatch: got '$($request.signingPolicySlug)', " +
|
|
"expected '$ExpectedSigningPolicySlug'."
|
|
)
|
|
}
|
|
|
|
$deadline = [DateTimeOffset]::UtcNow.AddSeconds($TimeoutSeconds)
|
|
$approvalSubmitted = $false
|
|
$externalApprovalNoticeWritten = $false
|
|
while ($true) {
|
|
$status = Invoke-RestMethod `
|
|
-Method Get `
|
|
-Uri "$requestBaseUrl/Status" `
|
|
-Headers $headers
|
|
Write-Host (
|
|
"SignPath request $SigningRequestId status: " +
|
|
"$($status.status) ($($status.workflowStatus))"
|
|
)
|
|
|
|
if ($status.isFinalStatus) {
|
|
if ($status.status -ne "Completed") {
|
|
throw (
|
|
"SignPath request ended with status '$($status.status)' " +
|
|
"and workflow status '$($status.workflowStatus)'."
|
|
)
|
|
}
|
|
break
|
|
}
|
|
|
|
if ($status.status -eq "WaitingForApproval" -and -not $approvalSubmitted) {
|
|
if ($WaitForExternalApproval) {
|
|
if (-not $externalApprovalNoticeWritten) {
|
|
Write-Host (
|
|
"Waiting for an authorized SignPath user to approve request " +
|
|
"$SigningRequestId."
|
|
)
|
|
$externalApprovalNoticeWritten = $true
|
|
}
|
|
} else {
|
|
Invoke-RestMethod `
|
|
-Method Post `
|
|
-Uri "$requestBaseUrl/Approve" `
|
|
-Headers $headers | Out-Null
|
|
$approvalSubmitted = $true
|
|
Write-Host "Approved SignPath request $SigningRequestId through the release CI identity."
|
|
}
|
|
}
|
|
|
|
if ([DateTimeOffset]::UtcNow -ge $deadline) {
|
|
throw "Timed out waiting for SignPath request $SigningRequestId."
|
|
}
|
|
Start-Sleep -Seconds $PollIntervalSeconds
|
|
}
|
|
|
|
$temporaryRoot = if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) {
|
|
[IO.Path]::GetTempPath()
|
|
} else {
|
|
$env:RUNNER_TEMP
|
|
}
|
|
$temporaryArchive = Join-Path $temporaryRoot "signpath-$SigningRequestId.zip"
|
|
try {
|
|
Invoke-WebRequest `
|
|
-Method Get `
|
|
-Uri "$requestBaseUrl/SignedArtifact" `
|
|
-Headers $headers `
|
|
-OutFile $temporaryArchive
|
|
if (-not (Test-Path -LiteralPath $temporaryArchive -PathType Leaf)) {
|
|
throw "SignPath did not return a signed artifact."
|
|
}
|
|
if ((Get-Item -LiteralPath $temporaryArchive).Length -eq 0) {
|
|
throw "SignPath returned an empty signed artifact."
|
|
}
|
|
|
|
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
|
|
Expand-Archive `
|
|
-LiteralPath $temporaryArchive `
|
|
-DestinationPath $outputDirectory `
|
|
-Force
|
|
} finally {
|
|
Remove-Item -LiteralPath $temporaryArchive -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Write-Host "Downloaded and extracted signed artifact to $outputDirectory."
|