Severity: Warning
File: .github/workflows/publish.yml lines 85-134
Description:
The publish pipeline downloads three third-party binaries from their GitHub release pages and either adds them to GITHUB_PATH or silently installs them, in each case without a SHA256 / signature check. This is distinct from #787 (which is about the Microsoft-hosted dotnet-install.ps1 in setup-dotnet/action.yml) — those three binaries come from different supply chains and each compromise vector is independent.
1. CycloneDX CLI (lines 85-97):
$v = $env:CYCLONEDX_CLI_VERSION
...
Invoke-WebRequest -Uri "https://github.com/CycloneDX/cyclonedx-cli/releases/download/$v/cyclonedx-win-x64.exe" `
-OutFile "$env:USERPROFILE\cyclonedx.exe" `
-ErrorAction Stop
Add-Content -Path $env:GITHUB_PATH -Value $env:USERPROFILE
The .exe is downloaded and then put on PATH. A subsequent SBOM-generation step invokes it directly, so compromise of github.com/CycloneDX/cyclonedx-cli release assets (release re-upload, tag-force-push, repo hijack) ends with arbitrary code running on the runner with access to all repo secrets (SIGNPATH_API_TOKEN, NUGET_API_KEY, etc.).
2. Inno Setup (lines 99-105):
$url = "https://github.com/jrsoftware/issrc/releases/download/is-6_6_1/innosetup-6.6.1.exe"
Invoke-WebRequest -Uri $url -OutFile "innosetup.exe" -ErrorAction Stop
Start-Process ".\innosetup.exe" -ArgumentList "/VERYSILENT", "/SUPPRESSMSGBOXES" -Wait
if (-not (Test-Path $env:ISCC)) { throw "ISCC.exe not found at $env:ISCC" }
A third-party installer is executed with /VERYSILENT. The post-check only verifies ISCC.exe exists at the expected path — a tampered installer could have already done anything else the build user is allowed to do (including hooking into later steps) before dropping the expected file.
3. 7-Zip (lines 107-133):
$version = "2600" # For version 26.00
$url = "https://github.com/ip7z/7zip/releases/download/26.00/7z$($version)-x64.exe"
$installer = "7z_setup.exe"
$installDir = "C:\7-Zip"
...
Invoke-WebRequest -Uri $url -OutFile $installer -ErrorAction Stop
...
Start-Process ".\$installer" -ArgumentList "/S", "/D=$installDir" -Wait
Same pattern: downloaded via Invoke-WebRequest, silently installed, used by the pipeline to package artifacts.
Threat model:
Each of the three upstream repos (CycloneDX/cyclonedx-cli, jrsoftware/issrc, ip7z/7zip) is an independent supply-chain dependency of every Servy release. A single release-asset compromise on any of them is enough to pivot into a signed Servy installer, because the publish job that calls SignPath runs after these steps on the same runner.
#607 addresses third-party Actions (uses: references) — that's a different attack surface. #787 addresses Microsoft-hosted dotnet-install.ps1 — also a different origin. These three binaries are not covered by either issue.
Suggested fix:
For each of the three downloads, pin an expected SHA256 and verify before execution:
$url = "https://github.com/CycloneDX/cyclonedx-cli/releases/download/$v/cyclonedx-win-x64.exe"
$expected = "<sha256 for this version, bumped in the same PR that bumps CYCLONEDX_CLI_VERSION>"
Invoke-WebRequest -Uri $url -OutFile cyclonedx.exe -ErrorAction Stop
$actual = (Get-FileHash cyclonedx.exe -Algorithm SHA256).Hash
if ($actual -ne $expected) {
throw "cyclonedx.exe integrity check failed. Expected $expected, got $actual."
}
Same idea for Inno Setup and 7-Zip. Bump the expected hash in the same PR that bumps the version. Alternatively, vendor the installers into a private release of this repo and pull from there, matching the hardening stance of #607.
Low-effort interim option for Inno Setup / 7-Zip: verify Authenticode signature before executing (Get-AuthenticodeSignature), which at least guarantees the binary was signed by the respective project's cert and not swapped mid-flight.
Severity: Warning
File:
.github/workflows/publish.ymllines 85-134Description:
The publish pipeline downloads three third-party binaries from their GitHub release pages and either adds them to
GITHUB_PATHor silently installs them, in each case without a SHA256 / signature check. This is distinct from #787 (which is about the Microsoft-hosteddotnet-install.ps1insetup-dotnet/action.yml) — those three binaries come from different supply chains and each compromise vector is independent.1. CycloneDX CLI (lines 85-97):
The
.exeis downloaded and then put on PATH. A subsequent SBOM-generation step invokes it directly, so compromise ofgithub.com/CycloneDX/cyclonedx-clirelease assets (release re-upload, tag-force-push, repo hijack) ends with arbitrary code running on the runner with access to all repo secrets (SIGNPATH_API_TOKEN,NUGET_API_KEY, etc.).2. Inno Setup (lines 99-105):
A third-party installer is executed with
/VERYSILENT. The post-check only verifiesISCC.exeexists at the expected path — a tampered installer could have already done anything else the build user is allowed to do (including hooking into later steps) before dropping the expected file.3. 7-Zip (lines 107-133):
Same pattern: downloaded via
Invoke-WebRequest, silently installed, used by the pipeline to package artifacts.Threat model:
Each of the three upstream repos (CycloneDX/cyclonedx-cli, jrsoftware/issrc, ip7z/7zip) is an independent supply-chain dependency of every Servy release. A single release-asset compromise on any of them is enough to pivot into a signed Servy installer, because the publish job that calls SignPath runs after these steps on the same runner.
#607addresses third-party Actions (uses:references) — that's a different attack surface.#787addresses Microsoft-hosteddotnet-install.ps1— also a different origin. These three binaries are not covered by either issue.Suggested fix:
For each of the three downloads, pin an expected SHA256 and verify before execution:
Same idea for Inno Setup and 7-Zip. Bump the expected hash in the same PR that bumps the version. Alternatively, vendor the installers into a private release of this repo and pull from there, matching the hardening stance of #607.
Low-effort interim option for Inno Setup / 7-Zip: verify Authenticode signature before executing (
Get-AuthenticodeSignature), which at least guarantees the binary was signed by the respective project's cert and not swapped mid-flight.