fix(install): verify release archive checksum before install (#38)

The first-run installers (install.sh, install.ps1) downloaded and
extracted the bsk release archive with no integrity check, even though
the in-app `bsk update` path treats a sha256 checksum as mandatory
(update.rs bails with "does not include a sha256 checksum; cannot
safely auto-update"). The release version.json already carries a
per-asset sha256 (render-version-json.mjs renders with --dist), so the
installers can verify the same way.

Both installers now fetch version.json once (pinned and latest), read
`assets[<platform>].sha256`, and verify the downloaded archive's sha256
before extracting:
- install.sh: POSIX sed to parse the field (no jq dependency), and
  sha256sum/shasum to compute the digest.
- install.ps1: Invoke-RestMethod + Get-FileHash.

A checksum mismatch is fatal (fail-closed, matching `bsk update`). A
missing manifest/checksum, or the absence of a sha256 tool, only skips
verification with a warning, so the first-run bootstrap still works on
minimal/offline machines.

Validation: `sh -n` / `bash -n`; functional test of the sed extraction
against a realistic version.json and of compute_sha256 against
sha256sum. The installers are not exercised by CI (no shell harness).

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Qiaochu Hu
2026-08-12 18:44:17 +08:00
committed by GitHub
co-authored by Claude
parent ac806ae8f4
commit c75f97119d
2 changed files with 96 additions and 26 deletions
+38 -17
View File
@@ -51,27 +51,14 @@ function Get-PlatformTriple {
}
return @{
ArchId = $archId
ArchId = $archId
TargetTriple = $windowsArch
PlatformKey = "windows-$archId"
}
}
# ── Version resolution ────────────────────────────────────────────────────────
function Get-LatestVersion {
$versionJsonUrl = "${GitHub}/releases/latest/download/version.json"
Write-Log "fetching latest version from ${versionJsonUrl}"
try {
$version = (Invoke-RestMethod -Uri $versionJsonUrl).version
if (-not $version) { Write-Die "could not parse version from version.json" }
return $version
}
catch {
Write-Die "failed to fetch version.json: $_"
}
}
# ── PATH helpers ──────────────────────────────────────────────────────────────
function Add-ToUserPath {
@@ -132,17 +119,40 @@ function Main {
if ($env:BSK_VERSION) {
$version = $env:BSK_VERSION -replace '^v', ''
$tag = "cli-v${version}"
$manifestUrl = "${GitHub}/releases/download/${tag}/version.json"
Write-Log "using pinned version ${version}"
# Best-effort manifest fetch for the checksum (missing manifest
# only skips verification; a mismatch is fatal below).
try { $manifest = Invoke-RestMethod -Uri $manifestUrl } catch { $manifest = $null }
}
else {
$version = Get-LatestVersion
$manifestUrl = "${GitHub}/releases/latest/download/version.json"
Write-Log "fetching latest version from ${manifestUrl}"
$manifest = Invoke-RestMethod -Uri $manifestUrl
$version = $manifest.version
if (-not $version) { Write-Die "could not parse version from version.json" }
$tag = "cli-v${version}"
Write-Log "latest version is ${version}"
}
$tag = "cli-v${version}"
$archiveName = "bsk-v${version}-$($platform.TargetTriple).zip"
$downloadUrl = "${GitHub}/releases/download/${tag}/${archiveName}"
$expectedSha = $null
$platformKey = $platform.PlatformKey
if ($manifest -and $manifest.assets) {
$expectedSha = $manifest.assets.$platformKey.sha256
}
if (-not $expectedSha) {
if (-not $manifest) {
Write-Log "warning: could not fetch version.json; skipping checksum verification"
}
else {
Write-Log "warning: no checksum published for $($platform.PlatformKey); skipping checksum verification"
}
}
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
@@ -152,6 +162,17 @@ function Main {
Write-Log "downloading ${downloadUrl}"
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath -UseBasicParsing -ErrorAction Stop
if ($expectedSha) {
Write-Log "verifying checksum"
$actualSha = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash
if ($actualSha -ieq $expectedSha) {
Write-Log "checksum OK"
}
else {
Write-Die "checksum mismatch: expected $expectedSha, got $actualSha"
}
}
Write-Log "extracting ${archiveName}"
Expand-Archive -Path $archivePath -DestinationPath $tempDir -Force