From bb091519da60a603bf4e0a783a7fc918748c9383 Mon Sep 17 00:00:00 2001 From: Michiel De Smet Date: Mon, 15 Jun 2026 21:20:26 +0800 Subject: [PATCH 1/5] feat(install): verify release archive checksums (both installers) Raises the integrity bar for the standalone installers (follow-up to #930). - release.yml: generate a checksums.txt (sha256sum format) over the release archives and publish it as a release asset. - install (bash) + install.ps1: fetch checksums.txt and verify the downloaded archive's SHA256 before extracting. Hard-fail on mismatch; soft-skip with a notice when checksums.txt is absent (older pinned releases) or unreachable, so existing version-pinned installs keep working. - Cross-platform sha in bash (sha256sum or shasum -a 256); Get-FileHash on Windows. Verification runs before extraction in both. - Tests: checksum-verification.test.ts asserts release.yml publishes the file and both installers fetch + compare + hard-fail on mismatch. Verified: bash -n clean; install.ps1 parses clean and the Pester suite (6/6) still passes on PowerShell 7.6.2. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 8 +++ install | 47 ++++++++++++++++++ install.ps1 | 46 ++++++++++++++--- .../install/checksum-verification.test.ts | 49 +++++++++++++++++++ 4 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 packages/opencode/test/install/checksum-verification.test.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84594be2e..52caa05e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -357,6 +357,13 @@ jobs: path: packages/opencode/dist/ merge-multiple: true + - name: Generate checksums + # Single checksums.txt (sha256sum format: " ") shipped + # as a release asset. The curl and PowerShell installers fetch it and verify + # the downloaded archive before extracting. + working-directory: packages/opencode/dist + run: sha256sum *.tar.gz *.zip > checksums.txt + - name: Create GitHub Release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 with: @@ -366,5 +373,6 @@ jobs: files: | packages/opencode/dist/*.tar.gz packages/opencode/dist/*.zip + packages/opencode/dist/checksums.txt env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/install b/install index 2cf359ba0..9c07c512c 100755 --- a/install +++ b/install @@ -356,6 +356,51 @@ download_with_progress() { return $ret } +# Verify the downloaded archive against the release's checksums.txt. +# Hard-fails on a real mismatch; soft-skips when checksums.txt can't be fetched +# (older release, network blip) or has no entry, so pinned installs of +# pre-checksums releases keep working. +verify_checksum() { + local file="$1" + local name="$2" + # $url ends in /$filename — strip it to get the release base, append checksums.txt. + local checksums_url="${url%/*}/checksums.txt" + + local sums + if ! sums=$(curl --fail -sL "$checksums_url" 2>/dev/null); then + print_message info "${MUTED}Skipping integrity check — checksums.txt not published for this release${NC}" + return 0 + fi + + # checksums.txt is sha256sum format: " " (sha256sum may + # prefix the name with '*' in binary mode — tolerate it). + local expected + expected=$(printf '%s\n' "$sums" | awk -v f="$name" '{ n=$2; sub(/^\*/,"",n); if (n==f) { print $1; exit } }') + if [ -z "$expected" ]; then + print_message info "${MUTED}Skipping integrity check — no checksum entry for $name${NC}" + return 0 + fi + + local actual + if command -v sha256sum >/dev/null 2>&1; then + actual=$(sha256sum "$file" | cut -d' ' -f1) + elif command -v shasum >/dev/null 2>&1; then + actual=$(shasum -a 256 "$file" | cut -d' ' -f1) + else + print_message info "${MUTED}Skipping integrity check — no sha256 tool available${NC}" + return 0 + fi + + if [ "$actual" != "$expected" ]; then + print_message error "Checksum mismatch for $name" + print_message error " expected: $expected" + print_message error " actual: $actual" + rm -rf "$tmp_dir" + exit 1 + fi + print_message info "${MUTED}Verified ${NC}$name${MUTED} (sha256)${NC}" +} + download_and_install() { print_message info "\n${MUTED}Installing ${NC}altimate ${MUTED}version: ${NC}$specific_version" local tmp_dir="${TMPDIR:-/tmp}/altimate_install_$$" @@ -367,6 +412,8 @@ download_and_install() { curl --fail -# -L -o "$tmp_dir/$filename" "$url" fi + verify_checksum "$tmp_dir/$filename" "$filename" + # Extract only the expected binary member rather than the whole archive. # The current build only puts a single file in each archive, but listing # the member explicitly makes a future "tars a whole directory" mistake diff --git a/install.ps1 b/install.ps1 index bcab7223f..4fcea8aa6 100644 --- a/install.ps1 +++ b/install.ps1 @@ -77,6 +77,36 @@ public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wP } } +# Verify a downloaded archive against the release's checksums.txt. +# Hard-fails (throws) on a real mismatch. Soft-skips when checksums.txt can't be +# fetched (older release, network blip) or has no entry for this file, so pinned +# installs of pre-checksums releases keep working. +function Test-Checksum { + param([string]$Path, [string]$Name, [string]$ChecksumsUrl) + + $sums = $null + try { + $sums = (Invoke-WebRequest -Uri $ChecksumsUrl -UseBasicParsing).Content + } catch { + Write-Muted "Skipping integrity check — checksums.txt not published for this release" + return + } + + # checksums.txt is sha256sum format: " " (one entry per line). + $line = ($sums -split "`n") | Where-Object { $_ -match "\s\*?$([regex]::Escape($Name))\s*$" } | Select-Object -First 1 + if (-not $line) { + Write-Muted "Skipping integrity check — no checksum entry for $Name" + return + } + + $expected = (($line -split '\s+')[0]).ToLower() + $actual = (Get-FileHash -Path $Path -Algorithm SHA256).Hash.ToLower() + if ($actual -ne $expected) { + throw "Checksum mismatch for $Name (expected $expected, got $actual)" + } + Write-Muted "Verified $Name (sha256)" +} + # --------------------------------------------------------------------------- # Architecture / baseline detection # --------------------------------------------------------------------------- @@ -171,10 +201,12 @@ function Install-Target { $filename = "$App-$target.zip" if ($useLatest) { - $url = "https://github.com/AltimateAI/altimate-code/releases/latest/download/$filename" + $base = "https://github.com/AltimateAI/altimate-code/releases/latest/download" } else { - $url = "https://github.com/AltimateAI/altimate-code/releases/download/v$specificVersion/$filename" + $base = "https://github.com/AltimateAI/altimate-code/releases/download/v$specificVersion" } + $url = "$base/$filename" + $checksumsUrl = "$base/checksums.txt" Write-Host "" Write-Host "Installing $App version: $specificVersion" @@ -184,12 +216,6 @@ function Install-Target { $zipPath = Join-Path $tmpDir $filename try { - # NOTE: integrity verification (SHA256/signature) of the archive is - # intentionally deferred to match the bash installer's posture — both rely - # on HTTPS from github.com release assets. Releases do not currently publish - # a checksums file; adding one + verifying it in both installers is tracked - # as a follow-up. See PR #930 discussion. - # # Prefer curl.exe (ships with Windows 10 1803+) for a fast download with # --fail so HTTP errors don't write an error page to disk; fall back to # Invoke-WebRequest where curl.exe is unavailable. @@ -201,6 +227,10 @@ function Install-Target { Invoke-WebRequest -Uri $url -OutFile $zipPath -UseBasicParsing } + # Integrity check: hard-fail on mismatch; skip (with notice) when the release + # predates checksums.txt or the fetch fails, so older pinned installs still work. + Test-Checksum -Path $zipPath -Name $filename -ChecksumsUrl $checksumsUrl + Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force $extracted = Join-Path $tmpDir $BinaryName if (-not (Test-Path $extracted)) { diff --git a/packages/opencode/test/install/checksum-verification.test.ts b/packages/opencode/test/install/checksum-verification.test.ts new file mode 100644 index 000000000..5fd26afe2 --- /dev/null +++ b/packages/opencode/test/install/checksum-verification.test.ts @@ -0,0 +1,49 @@ +/** + * Release-archive integrity verification across the install surface. + * + * The release publishes a checksums.txt asset; both installers fetch it and + * verify the downloaded archive (sha256) before extracting — hard-fail on + * mismatch, soft-skip when the file is absent (older pinned releases). + */ +import { describe, test, expect } from "bun:test" +import { readFileSync } from "node:fs" +import { join } from "node:path" + +const REPO_ROOT = join(import.meta.dir, "../../../..") +const BASH_INSTALL = readFileSync(join(REPO_ROOT, "install"), "utf-8") +const PS1 = readFileSync(join(REPO_ROOT, "install.ps1"), "utf-8") +const RELEASE_YML = readFileSync(join(REPO_ROOT, ".github/workflows/release.yml"), "utf-8") + +describe("release publishes checksums", () => { + test("release.yml generates checksums.txt and uploads it", () => { + expect(RELEASE_YML).toContain("sha256sum *.tar.gz *.zip > checksums.txt") + expect(RELEASE_YML).toContain("packages/opencode/dist/checksums.txt") + }) +}) + +describe("bash installer verifies checksums", () => { + test("fetches checksums.txt and compares sha256", () => { + expect(BASH_INSTALL).toContain("checksums.txt") + expect(BASH_INSTALL).toMatch(/sha256sum|shasum -a 256/) + }) + + test("hard-fails on mismatch", () => { + expect(BASH_INSTALL).toContain("Checksum mismatch") + expect(BASH_INSTALL).toContain("verify_checksum") + }) +}) + +describe("PowerShell installer verifies checksums", () => { + test("fetches checksums.txt and compares sha256", () => { + expect(PS1).toContain("checksums.txt") + expect(PS1).toContain("Get-FileHash") + expect(PS1).toContain("Test-Checksum") + }) + + test("hard-fails on mismatch before extracting", () => { + expect(PS1).toContain("Checksum mismatch") + // The verify call must precede the actual extraction call (not the + // Expand-Archive mention in the top-of-file ProgressPreference comment). + expect(PS1.indexOf("Test-Checksum -Path")).toBeLessThan(PS1.indexOf("Expand-Archive -Path")) + }) +}) From cecfe069121869143e93399ad013ba28c2d07f8c Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 18 Jun 2026 16:25:27 +0800 Subject: [PATCH 2/5] fix(install): address checksum-verification review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - install.ps1: decode a Byte[] checksums.txt body so verification works on Windows PowerShell 5.1. GitHub serves release assets as octet-stream, so on PS 5.1 Invoke-WebRequest returns .Content as Byte[]; it coerced to a decimal string and every check silently soft-skipped (sahrizvi, P1). - install.ps1: pin the archive and checksums.txt to the resolved release tag instead of the mutable latest/ URL, so a release published mid-install can't hand back mismatched assets and trigger a spurious hard-fail (cubic, P2). Falls back to latest/ only when the version can't be resolved. - install: in verify_checksum, clean up via $(dirname "$file") rather than the caller's dynamically-scoped $tmp_dir local — self-contained (cubic, P2). - tests: Pester coverage for Test-Checksum (String + Byte[] + mismatch paths, verified to fail without the decode) and TS guards for the decode and the PowerShell same-release pinning. Note: the bash installer is intentionally left on the latest/download path here to keep this PR disjoint from #946 (which owns the bash latest-version block); the two PRs then merge in either order with no conflict. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019M7GkS3bYZaFhEbBhVTecG --- install | 5 +- install.ps1 | 20 +++++- .../install/checksum-verification.test.ts | 26 +++++++ test/windows/install.Tests.ps1 | 69 +++++++++++++++++++ 4 files changed, 117 insertions(+), 3 deletions(-) diff --git a/install b/install index 9c07c512c..31cad4517 100755 --- a/install +++ b/install @@ -395,7 +395,10 @@ verify_checksum() { print_message error "Checksum mismatch for $name" print_message error " expected: $expected" print_message error " actual: $actual" - rm -rf "$tmp_dir" + # Clean up via the file's own directory rather than the caller's $tmp_dir, + # so this stays self-contained and doesn't depend on a dynamically-scoped + # local from download_and_install. + rm -rf "$(dirname "$file")" exit 1 fi print_message info "${MUTED}Verified ${NC}$name${MUTED} (sha256)${NC}" diff --git a/install.ps1 b/install.ps1 index 4fcea8aa6..765b84ff4 100644 --- a/install.ps1 +++ b/install.ps1 @@ -86,7 +86,17 @@ function Test-Checksum { $sums = $null try { - $sums = (Invoke-WebRequest -Uri $ChecksumsUrl -UseBasicParsing).Content + $resp = Invoke-WebRequest -Uri $ChecksumsUrl -UseBasicParsing + # On Windows PowerShell 5.1, .Content is a Byte[] (not a String) whenever the + # response isn't a text-recognized content-type — and GitHub serves release + # assets as application/octet-stream. A raw Byte[] coerces to a "49 50 51 …" + # decimal string when split, so verification would silently soft-skip on the + # default Windows shell. Decode the bytes explicitly to recover real text. + if ($resp.Content -is [byte[]]) { + $sums = [System.Text.Encoding]::UTF8.GetString($resp.Content) + } else { + $sums = $resp.Content + } } catch { Write-Muted "Skipping integrity check — checksums.txt not published for this release" return @@ -200,7 +210,13 @@ function Install-Target { if ($Baseline) { $target = "$target-baseline" } $filename = "$App-$target.zip" - if ($useLatest) { + # Pin BOTH the archive and checksums.txt to the same resolved release. The + # mutable releases/latest/download URL would fetch the two assets in separate + # requests, so a release published mid-install could hand back an archive from + # one release and checksums from another → a spurious hard-fail. We resolve + # the concrete tag up front ($specificVersion), so pin to it. Only fall back + # to the mutable latest/ URL when the version genuinely couldn't be resolved. + if ($useLatest -and -not $specificVersion) { $base = "https://github.com/AltimateAI/altimate-code/releases/latest/download" } else { $base = "https://github.com/AltimateAI/altimate-code/releases/download/v$specificVersion" diff --git a/packages/opencode/test/install/checksum-verification.test.ts b/packages/opencode/test/install/checksum-verification.test.ts index 5fd26afe2..149ff57a5 100644 --- a/packages/opencode/test/install/checksum-verification.test.ts +++ b/packages/opencode/test/install/checksum-verification.test.ts @@ -46,4 +46,30 @@ describe("PowerShell installer verifies checksums", () => { // Expand-Archive mention in the top-of-file ProgressPreference comment). expect(PS1.indexOf("Test-Checksum -Path")).toBeLessThan(PS1.indexOf("Expand-Archive -Path")) }) + + test("decodes a Byte[] checksums.txt body (Windows PowerShell 5.1)", () => { + // GitHub serves release assets as octet-stream, so PS 5.1 returns .Content + // as Byte[]; without an explicit decode it coerces to decimal text and the + // check silently soft-skips. See test/windows/install.Tests.ps1 for the + // behavioral guard. + expect(PS1).toContain("-is [byte[]]") + expect(PS1).toContain("[System.Text.Encoding]::UTF8.GetString") + }) +}) + +describe("archive and checksums come from the same release (no latest/ race)", () => { + test("bash derives the checksums URL from the same base as the archive", () => { + // verify_checksum builds checksums_url from the archive's own URL (${url%/*}), + // so the two are always fetched from the same release path. + expect(BASH_INSTALL).toContain('checksums_url="${url%/*}/checksums.txt"') + }) + + test("PowerShell pins both URLs to the resolved release tag (cubic P2)", () => { + // The archive and checksums.txt share one $base; that base is the resolved + // tag, so a release published mid-install can't hand back mismatched assets. + // Falls back to latest/ only when the version couldn't be resolved. + expect(PS1).toContain('$url = "$base/$filename"') + expect(PS1).toContain('$checksumsUrl = "$base/checksums.txt"') + expect(PS1).toContain('$base = "https://github.com/AltimateAI/altimate-code/releases/download/v$specificVersion"') + }) }) diff --git a/test/windows/install.Tests.ps1 b/test/windows/install.Tests.ps1 index 100892007..edb60df1c 100644 --- a/test/windows/install.Tests.ps1 +++ b/test/windows/install.Tests.ps1 @@ -96,3 +96,72 @@ Describe "install.ps1 version handling" { $r.Output | Should -Match "Available releases" } } + +Describe "install.ps1 Test-Checksum" { + # Exercise the real Test-Checksum function in isolation. install.ps1 runs + # top-to-bottom (arch detection, version resolution, exit) so it can't just be + # dot-sourced; instead extract the function via the AST and define it here, + # alongside a recording Write-Muted stub and a fake Invoke-WebRequest that + # returns canned content. + BeforeAll { + $src = Get-Content -Raw $script:InstallScript + $tokens = $null; $errors = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput($src, [ref]$tokens, [ref]$errors) + $def = $ast.Find({ + param($n) + $n -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $n.Name -eq "Test-Checksum" + }, $true) + if (-not $def) { throw "Test-Checksum not found in install.ps1" } + . ([ScriptBlock]::Create($def.Extent.Text)) + + # Records what Test-Checksum reports, so we can tell a real "Verified" from a + # silent "Skipping integrity check" soft-skip. + $script:Muted = [System.Collections.Generic.List[string]]::new() + function Write-Muted { param([string]$Message) $script:Muted.Add($Message) } + + # Fake Invoke-WebRequest: a function shadows the cmdlet, returning whatever + # $script:FakeContent is set to (string or Byte[]) as .Content. + function Invoke-WebRequest { param($Uri, [switch]$UseBasicParsing) [pscustomobject]@{ Content = $script:FakeContent } } + + function New-FixtureArchive { + $tmp = New-TemporaryFile + "altimate-archive-fixture" | Set-Content -NoNewline -Path $tmp + return $tmp + } + } + + BeforeEach { $script:Muted.Clear() } + + It "verifies a matching archive when checksums.txt is served as a String (PS 7)" { + $tmp = New-FixtureArchive + $name = Split-Path $tmp -Leaf + $hash = (Get-FileHash -Path $tmp -Algorithm SHA256).Hash.ToLower() + $script:FakeContent = "$hash $name`n" + { Test-Checksum -Path $tmp -Name $name -ChecksumsUrl "https://x/checksums.txt" } | Should -Not -Throw + ($script:Muted -join "`n") | Should -Match "Verified" + ($script:Muted -join "`n") | Should -Not -Match "Skipping" + Remove-Item $tmp -Force + } + + It "verifies a matching archive when checksums.txt is served as Byte[] (Windows PowerShell 5.1)" { + # The regression guard: GitHub serves release assets as octet-stream, so on + # PS 5.1 .Content is a Byte[]. Without the explicit UTF8 decode it coerces to + # a "49 50 51 …" decimal string, no entry matches, and the check soft-skips. + $tmp = New-FixtureArchive + $name = Split-Path $tmp -Leaf + $hash = (Get-FileHash -Path $tmp -Algorithm SHA256).Hash.ToLower() + $script:FakeContent = [System.Text.Encoding]::UTF8.GetBytes("$hash $name`n") + { Test-Checksum -Path $tmp -Name $name -ChecksumsUrl "https://x/checksums.txt" } | Should -Not -Throw + ($script:Muted -join "`n") | Should -Match "Verified" + ($script:Muted -join "`n") | Should -Not -Match "Skipping" + Remove-Item $tmp -Force + } + + It "hard-fails on a real checksum mismatch (Byte[] content)" { + $tmp = New-FixtureArchive + $name = Split-Path $tmp -Leaf + $script:FakeContent = [System.Text.Encoding]::UTF8.GetBytes((("0" * 64) + " $name`n")) + { Test-Checksum -Path $tmp -Name $name -ChecksumsUrl "https://x/checksums.txt" } | Should -Throw + Remove-Item $tmp -Force + } +} From d9fe5be74cd8cdd1e4791707531969bb13fa3591 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 18 Jun 2026 17:07:48 +0800 Subject: [PATCH 3/5] fix(install.ps1): ASCII-only so it parses on Windows PowerShell 5.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install.ps1 had no BOM and used a few non-ASCII characters (em dash, ellipsis, right arrow) in comments and messages. Windows PowerShell 5.1 — the default shell on Windows 10 and preinstalled on Windows 11 — reads a BOM-less file as the system ANSI codepage, not UTF-8, so those multi-byte characters corrupt the token stream and the whole script fails to parse (verified on real PS 5.1: "The '<' operator is reserved", cascading to "Missing closing '}'"). This is a pre-existing issue (the characters predate this PR) that CI doesn't catch because the Pester job runs under pwsh (PowerShell 7, UTF-8 by default). Replacing the three characters with ASCII equivalents (-, ..., ->) makes the installer parse and run on PS 5.1 while keeping pwsh behavior identical. Verified end-to-end on real Windows PowerShell 5.1: resolve version -> download -> extract -> place the binary all succeed. Same transliteration is applied verbatim in #946 so the two PRs merge cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019M7GkS3bYZaFhEbBhVTecG --- install.ps1 | 24 ++++++++++++------------ test/windows/install.Tests.ps1 | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/install.ps1 b/install.ps1 index 765b84ff4..0f00fbf63 100644 --- a/install.ps1 +++ b/install.ps1 @@ -3,7 +3,7 @@ # # Mirrors ./install (the bash installer for macOS/Linux): it downloads the # Bun-compiled standalone executable (altimate.exe) from GitHub releases and -# drops it in %USERPROFILE%\.altimate\bin — it does NOT depend on npm/Node. +# drops it in %USERPROFILE%\.altimate\bin - it does NOT depend on npm/Node. # # Usage: # powershell -c "irm https://www.altimate.sh/install.ps1 | iex" @@ -64,8 +64,8 @@ if ($Help) { exit 0 } -# A single P/Invoke type carries both native calls we need — the AVX2 CPU probe -# (kernel32) and the PATH-change broadcast (user32) — so we Add-Type once instead +# A single P/Invoke type carries both native calls we need - the AVX2 CPU probe +# (kernel32) and the PATH-change broadcast (user32) - so we Add-Type once instead # of compiling a throwaway type per call site. function Initialize-Native { if (-not ("Win32.AltimateNative" -as [type])) { @@ -88,8 +88,8 @@ function Test-Checksum { try { $resp = Invoke-WebRequest -Uri $ChecksumsUrl -UseBasicParsing # On Windows PowerShell 5.1, .Content is a Byte[] (not a String) whenever the - # response isn't a text-recognized content-type — and GitHub serves release - # assets as application/octet-stream. A raw Byte[] coerces to a "49 50 51 …" + # response isn't a text-recognized content-type - and GitHub serves release + # assets as application/octet-stream. A raw Byte[] coerces to a "49 50 51 ..." # decimal string when split, so verification would silently soft-skip on the # default Windows shell. Decode the bytes explicitly to recover real text. if ($resp.Content -is [byte[]]) { @@ -98,14 +98,14 @@ function Test-Checksum { $sums = $resp.Content } } catch { - Write-Muted "Skipping integrity check — checksums.txt not published for this release" + Write-Muted "Skipping integrity check - checksums.txt not published for this release" return } # checksums.txt is sha256sum format: " " (one entry per line). $line = ($sums -split "`n") | Where-Object { $_ -match "\s\*?$([regex]::Escape($Name))\s*$" } | Select-Object -First 1 if (-not $line) { - Write-Muted "Skipping integrity check — no checksum entry for $Name" + Write-Muted "Skipping integrity check - no checksum entry for $Name" return } @@ -141,14 +141,14 @@ function Test-Avx2 { Initialize-Native return [bool][Win32.AltimateNative]::IsProcessorFeaturePresent(40) } catch { - # If detection fails, assume no AVX2 and fall back to the baseline build — + # If detection fails, assume no AVX2 and fall back to the baseline build - # the baseline binary runs everywhere, an AVX2 binary on a non-AVX2 CPU crashes. return $false } } # --------------------------------------------------------------------------- -# Resolve version (once) — latest tag or a pinned release +# Resolve version (once) - latest tag or a pinned release # --------------------------------------------------------------------------- if ([string]::IsNullOrWhiteSpace($Version)) { $useLatest = $true @@ -213,7 +213,7 @@ function Install-Target { # Pin BOTH the archive and checksums.txt to the same resolved release. The # mutable releases/latest/download URL would fetch the two assets in separate # requests, so a release published mid-install could hand back an archive from - # one release and checksums from another → a spurious hard-fail. We resolve + # one release and checksums from another -> a spurious hard-fail. We resolve # the concrete tag up front ($specificVersion), so pin to it. Only fall back # to the mutable latest/ URL when the version genuinely couldn't be resolved. if ($useLatest -and -not $specificVersion) { @@ -256,7 +256,7 @@ function Install-Target { # Windows locks a running .exe, so `altimate upgrade` (which re-runs this # installer) can't overwrite the binary that is currently executing. Windows - # *does* allow renaming a running exe — move the old one aside first, then + # *does* allow renaming a running exe - move the old one aside first, then # drop the new one in. Best-effort cleanup of the stale copy afterward. if (Test-Path $InstalledBinary) { $stale = "$InstalledBinary.old" @@ -283,7 +283,7 @@ if (-not $needsBaseline) { & $InstalledBinary --version *> $null $code = $LASTEXITCODE if ($code -eq 3221225501 -or $code -eq 1073741795 -or $code -eq -1073741795) { - Write-Muted "CPU lacks AVX2 — reinstalling the baseline build" + Write-Muted "CPU lacks AVX2 - reinstalling the baseline build" Install-Target -Baseline:$true } } diff --git a/test/windows/install.Tests.ps1 b/test/windows/install.Tests.ps1 index edb60df1c..9abea83e2 100644 --- a/test/windows/install.Tests.ps1 +++ b/test/windows/install.Tests.ps1 @@ -1,7 +1,7 @@ # Pester behavioral tests for install.ps1 (the Windows standalone installer). # # These run the real script as a subprocess on Windows PowerShell so they -# exercise actual behavior — not just substring matching. They deliberately +# exercise actual behavior - not just substring matching. They deliberately # stop the script early (via -Help or an unknown -Version) so no 268 MB binary # is ever downloaded, while still covering the risky branches: argument # parsing, the WOW64 architecture fix, and unknown-version rejection. @@ -56,7 +56,7 @@ Describe "install.ps1 -Help" { Describe "install.ps1 architecture detection" { It "detects AMD64 under WOW64 (32-bit PowerShell on 64-bit Windows)" { - # PROCESSOR_ARCHITECTURE=x86 but PROCESSOR_ARCHITEW6432=AMD64 → real 64-bit box. + # PROCESSOR_ARCHITECTURE=x86 but PROCESSOR_ARCHITEW6432=AMD64 -> real 64-bit box. # Using an unknown version makes the script stop at the release 404 check, # which it can only reach if the WOW64 arch check let it past. $r = Invoke-Installer -ScriptArgs @("-Version", "0.0.0-nonexistent") -Env @{ @@ -146,7 +146,7 @@ Describe "install.ps1 Test-Checksum" { It "verifies a matching archive when checksums.txt is served as Byte[] (Windows PowerShell 5.1)" { # The regression guard: GitHub serves release assets as octet-stream, so on # PS 5.1 .Content is a Byte[]. Without the explicit UTF8 decode it coerces to - # a "49 50 51 …" decimal string, no entry matches, and the check soft-skips. + # a "49 50 51 ..." decimal string, no entry matches, and the check soft-skips. $tmp = New-FixtureArchive $name = Split-Path $tmp -Leaf $hash = (Get-FileHash -Path $tmp -Algorithm SHA256).Hash.ToLower() From ab14d6bcc19b73edac7966a21235c8584bf48148 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 18 Jun 2026 17:16:43 +0800 Subject: [PATCH 4/5] fix(install): guard the verify_checksum cleanup against a pathological path Defensive depth (coderabbit): only `rm -rf` the cleanup dir when dirname resolves to a real subdirectory, never "." or "/", so an unexpectedly empty or root-level $file can't wipe the cwd or worse. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019M7GkS3bYZaFhEbBhVTecG --- install | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/install b/install index 31cad4517..e228ac8cf 100755 --- a/install +++ b/install @@ -397,8 +397,13 @@ verify_checksum() { print_message error " actual: $actual" # Clean up via the file's own directory rather than the caller's $tmp_dir, # so this stays self-contained and doesn't depend on a dynamically-scoped - # local from download_and_install. - rm -rf "$(dirname "$file")" + # local from download_and_install. Guard against a pathological $file + # (empty or root-level) that would make dirname resolve to "." or "/". + local cleanup_dir + cleanup_dir=$(dirname "$file") + if [ -n "$cleanup_dir" ] && [ "$cleanup_dir" != "." ] && [ "$cleanup_dir" != "/" ]; then + rm -rf "$cleanup_dir" + fi exit 1 fi print_message info "${MUTED}Verified ${NC}$name${MUTED} (sha256)${NC}" From 1ecf6aeca35173574a66d89999d726c4a8aedfcc Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 18 Jun 2026 17:34:31 +0800 Subject: [PATCH 5/5] test: update #930 release-validation for the checksum URL refactor The #952 release-validation suite asserted the exact #930 URL literals. This PR builds the archive and checksums.txt from a shared $base (so they always come from the same release), so update those assertions to the $base/$url form, and convert the now-obsolete "verification deferred" test.todo into a real assertion that Test-Checksum verifies SHA256 before extraction. (This test never ran on this PR until it was retargeted from the merged feat/windows-powershell-installer branch to main.) Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019M7GkS3bYZaFhEbBhVTecG --- .../windows-installer-930-codex.test.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/opencode/test/release-validation/windows-installer-930-codex.test.ts b/packages/opencode/test/release-validation/windows-installer-930-codex.test.ts index 92e609129..8f429a3df 100644 --- a/packages/opencode/test/release-validation/windows-installer-930-codex.test.ts +++ b/packages/opencode/test/release-validation/windows-installer-930-codex.test.ts @@ -24,8 +24,12 @@ function upgradePowershellBlock() { describe("PR #930 install.ps1 release URL construction", () => { test("uses only HTTPS GitHub release URLs for Windows zip assets", () => { - expect(INSTALL_PS1).toContain('"https://github.com/AltimateAI/altimate-code/releases/latest/download/$filename"') - expect(INSTALL_PS1).toContain('"https://github.com/AltimateAI/altimate-code/releases/download/v$specificVersion/$filename"') + // The archive and checksums.txt share one $base so they always resolve to the + // same release (see verify_checksum / Test-Checksum). $base is the latest + // download path or the pinned release tag; $url and $checksumsUrl derive from it. + expect(INSTALL_PS1).toContain('$base = "https://github.com/AltimateAI/altimate-code/releases/latest/download"') + expect(INSTALL_PS1).toContain('$base = "https://github.com/AltimateAI/altimate-code/releases/download/v$specificVersion"') + expect(INSTALL_PS1).toContain('$url = "$base/$filename"') expect(INSTALL_PS1).toContain('"https://api.github.com/repos/AltimateAI/altimate-code/releases/latest"') expect(INSTALL_PS1).not.toMatch(/http:\/\/(?:github\.com|api\.github\.com|www\.altimate\.sh)/) }) @@ -62,9 +66,13 @@ describe("PR #930 install.ps1 release URL construction", () => { }) describe("PR #930 install.ps1 download and archive safety", () => { - // BUG: install.ps1 currently documents that SHA256/signature verification is deferred - // and relies only on HTTPS. Release assets should be verified before extraction. - test.todo("verifies downloaded archive integrity with SHA256 or a signature before extraction", () => {}) + test("verifies downloaded archive integrity with SHA256 before extraction", () => { + // Closed by the checksum-verification work: Test-Checksum fetches checksums.txt + // and compares SHA256, and the verify call precedes the actual extraction. + expect(INSTALL_PS1).toContain("Test-Checksum -Path $zipPath") + expect(INSTALL_PS1).toContain("Get-FileHash -Path $Path -Algorithm SHA256") + expect(INSTALL_PS1.indexOf("Test-Checksum -Path")).toBeLessThan(INSTALL_PS1.indexOf("Expand-Archive -Path")) + }) test("fails curl.exe downloads on HTTP errors and checks curl exit status", () => { const installTarget = scriptBlock("function Install-Target", "$needsBaseline")