From 19cd2c0aa2e462914e7ebf5eb07795b010cf616f Mon Sep 17 00:00:00 2001 From: Joe Petrucelli Date: Tue, 4 Aug 2026 09:10:40 -0400 Subject: [PATCH] ci(release): the published installer can be verified, and its build is pinned Pin PyInstaller and Inno Setup to specific known-good versions instead of floating on whatever the runner happens to have, and SHA-pin the three actions this workflow uses so a compromised tag can't swap in different code under the same version label. Pass the workflow_dispatch version input through the environment instead of interpolating it into the PowerShell script body, so a crafted dispatch input is read as data rather than executed. After the installer is built, hash it with SHA256 and carry that hash through: into the dry-run artifact alongside the exe, and into the published release notes under a "Verify the download" section, so a download can be checked without having to trust the hosting alone. Release notes are regenerated from scratch on every run so a re-run never appends a second checksum block to notes that already have one. installer/agent-audit.iss now templates the app name via the existing {#AppName} define on the three lines that previously hardcoded it, so the installer filename, install icon, and packaged source name track a single source of truth. --- .github/workflows/release.yml | 51 ++++++++++++++++++++++++++++------- installer/agent-audit.iss | 6 ++--- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ddda66..7eb6e6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,16 +25,18 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.12" - name: Take the version from the tag, or from the dispatch input + env: + DISPATCH_VERSION: ${{ inputs.version }} run: | if ($env:GITHUB_EVENT_NAME -eq 'workflow_dispatch') { - $v = "${{ inputs.version }}" + $v = $env:DISPATCH_VERSION } else { $v = $env:GITHUB_REF_NAME -replace '^v', '' } @@ -53,7 +55,7 @@ jobs: - name: Install agent-audit and PyInstaller run: | python -m pip install --upgrade pip - python -m pip install . pyinstaller + python -m pip install . pyinstaller==6.21.0 - name: Build the one-file executable # The entry point is a module function, so PyInstaller is aimed at @@ -87,7 +89,7 @@ jobs: - name: Install Inno Setup # Installed, not assumed: a runner image that quietly drops a # preinstalled tool would break releases with no warning. - run: choco install innosetup -y --no-progress + run: choco install innosetup -y --no-progress --version=6.7.1 - name: Build the installer run: | @@ -96,13 +98,27 @@ jobs: if (-not (Test-Path $iscc)) { throw "ISCC.exe not found at $iscc - Inno Setup's install layout changed" } & $iscc /DAppVersion=$env:VERSION installer\agent-audit.iss + - name: Checksum the installer + # A binary nobody can verify is a binary nobody should trust. The hash is + # written beside the exe, shipped as a release asset, and repeated in the + # release notes so a download can be checked without fetching the asset. + run: | + $exe = "dist\agent-audit-setup-$env:VERSION.exe" + if (-not (Test-Path $exe)) { throw "no installer at $exe" } + $leaf = Split-Path $exe -Leaf + $hash = (Get-FileHash $exe -Algorithm SHA256).Hash.ToLower() + "$hash $leaf" | Out-File -FilePath dist\SHA256SUMS -Encoding ascii + "INSTALLER_SHA256=$hash" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + - name: Keep the installer as an artifact (dry run only) # A dry run proves the build; it deliberately publishes nothing. if: github.event_name == 'workflow_dispatch' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent-audit-setup-${{ env.VERSION }} - path: dist/agent-audit-setup-${{ env.VERSION }}.exe + path: | + dist/agent-audit-setup-${{ env.VERSION }}.exe + dist/SHA256SUMS if-no-files-found: error - name: Attach the installer to the release @@ -115,10 +131,27 @@ jobs: run: | $ErrorActionPreference = 'Continue' $exe = "dist\agent-audit-setup-$env:VERSION.exe" + $leaf = Split-Path $exe -Leaf + # Notes are rebuilt from scratch every run, so re-running a release cannot + # append a second checksum block to notes that already carry one. + $generated = gh api "repos/$env:GITHUB_REPOSITORY/releases/generate-notes" -f tag_name=$env:GITHUB_REF_NAME -q .body + if ($LASTEXITCODE -ne 0) { throw "could not generate release notes for $env:GITHUB_REF_NAME" } + $notes = "dist\release-notes.md" + @( + $generated + "" + "## Verify the download" + "" + '```' + "$env:INSTALLER_SHA256 $leaf" + '```' + ) | Set-Content -Path $notes -Encoding utf8 gh release view $env:GITHUB_REF_NAME --json tagName | Out-Null if ($LASTEXITCODE -eq 0) { - gh release upload $env:GITHUB_REF_NAME $exe --clobber + gh release upload $env:GITHUB_REF_NAME $exe dist\SHA256SUMS --clobber + if ($LASTEXITCODE -ne 0) { throw "could not upload $exe to $env:GITHUB_REF_NAME" } + gh release edit $env:GITHUB_REF_NAME --notes-file $notes } else { - gh release create $env:GITHUB_REF_NAME --verify-tag --generate-notes $exe + gh release create $env:GITHUB_REF_NAME --verify-tag --notes-file $notes $exe dist\SHA256SUMS } if ($LASTEXITCODE -ne 0) { throw "could not publish $exe to $env:GITHUB_REF_NAME" } diff --git a/installer/agent-audit.iss b/installer/agent-audit.iss index 43206e4..3c6d07f 100644 --- a/installer/agent-audit.iss +++ b/installer/agent-audit.iss @@ -42,14 +42,14 @@ ArchitecturesInstallIn64BitMode=x64compatible ; edit below without a logoff. ChangesEnvironment=yes UninstallDisplayName={#AppName} {#AppVersion} -UninstallDisplayIcon={app}\agent-audit.exe +UninstallDisplayIcon={app}\{#AppName}.exe OutputDir=..\dist -OutputBaseFilename=agent-audit-setup-{#AppVersion} +OutputBaseFilename={#AppName}-setup-{#AppVersion} Compression=lzma SolidCompression=yes [Files] -Source: "..\dist\agent-audit.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\dist\{#AppName}.exe"; DestDir: "{app}"; Flags: ignoreversion [Registry] ; Append the install directory to the machine PATH. The Check skips the append