Skip to content

fix(installer): bootstrap the amd64 cosign on Windows-on-ARM — the arm64 asset does not exist (client#734) - #735

Merged
LukasWodka merged 2 commits into
developfrom
fix/cosign-windows-arm64-asset
Aug 17, 2026
Merged

fix(installer): bootstrap the amd64 cosign on Windows-on-ARM — the arm64 asset does not exist (client#734)#735
LukasWodka merged 2 commits into
developfrom
fix/cosign-windows-arm64-asset

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Closes#734. Unblocks the cli port in tracebloc/backend#2078.

The bug

Resolve-Cosign built cosign-windows-$arch.exe. On Windows-on-ARM that asks for cosign-windows-arm64.exean asset sigstore has never published:

v2.4.1 (pinned) → cosign-windows-amd64.exe (only Windows asset)
v3.1.3 (latest) → cosign-windows-amd64.exe (only Windows asset)

404 → Resolve-Cosign returns $null → fail closed. The direction is right; the reason is accidental. The user sees a cosign download failure that reads as a transient network problem, and no amount of retrying can conjure the asset.

Why now

backend#2078 proposes porting this exact function into cli/scripts/install.ps1, which today skips signature verification altogether. Copying it as-is would carry the broken arch map into a second installer — and the CLI is the more exposed one, because its release matrix really does build windows/arm64. So ARM users get a native binary and nothing able to verify it. Fixing it here means that port is a copy of one correct function.

Why amd64-under-emulation is the right answer, not a compromise

cosign verifies a signature over bytes. The instruction set it was compiled for cannot change the verdict, and the artifact handed to it is still the native arm64 binary. The bootstrapped cosign is checksum-verified against sigstore's own published list before use, exactly as on amd64 — the trust chain is byte-for-byte identical.

The alternative — fail closed with a winget install sigstore.cosign hint — routes the user to do the same thing by hand, because winget's cosign is also amd64-only. Same end state, more friction, and one more opportunity to reach for TRACEBLOC_ALLOW_UNVERIFIED=1, which is the outcome we least want.

Second half: a verifier that won't start is not a bad signature

Invoke-CosignVerifyBlob already fails closed on a binary that cannot execute — but reported it identically to a signature that did not verify. On an older Windows 10 on ARM without x64 emulation, that tells a user their download may be tampered with when the truth is "the verifier won't start on this machine."

Those warrant opposite reactions, and only one of them is a security event. A cosign version probe after resolution separates them:

  • still fails closed
  • says plainly that this is not a failed verification
  • names x64 emulation as the likely cause and gives a remedy that works
  • honours TRACEBLOC_ALLOW_UNVERIFIED loudly, like the sibling path

Test plan

make check green · Pester 680 passed, 0 failed across the suite.

Both halves mutation-real:

--- restore $asset = "cosign-windows-$arch.exe" ---
[-] requests the amd64 cosign asset unconditionally — never a per-arch name
--- drop the Test-CosignRuns probe ---
[-] fails closed, and says the download is NOT implicated
[-] names the real cause and a remedy that works on Windows-on-ARM
[-] never reaches the verify when cosign can't run
[-] honours TRACEBLOC_ALLOW_UNVERIFIED, loudly, like the other unverified path

Also pinned: the unknown-architecture refusal still stands, and the bootstrapped cosign is still checksum-verified — so the fix cannot be mistaken for loosening the trust chain.

Reviewer note

CI cannot exercise the ARM path — GitHub's Windows runners are x64, so no test here will ever run on the hardware this fixes. The arch-map coverage is deliberately source-level for that reason, and the comment inside Resolve-Cosign is load-bearing: it is the only thing that will stop someone "correcting" amd64 back to $arch.

🤖 Generated with Claude Code


Note

Medium Risk
Touches the installer’s cosign bootstrap and manifest signature gate (supply-chain trust root); behavior is more permissive on ARM when amd64 cosign runs under emulation, but trust chain and fail-closed defaults remain.

Overview
Fixes Windows-on-ARM installs that failed because Resolve-Cosign requested cosign-windows-arm64.exe, which Sigstore never ships—only cosign-windows-amd64.exe exists. Both AMD64 and ARM64 now bootstrap that amd64 asset; checksum verification of the bootstrapped binary is unchanged.

Adds Test-CosignRuns (cosign version with the same LASTEXITCODE sentinel as verify) and runs it in Confirm-ManifestSignature before any verify-blob. If cosign cannot execute (AV quarantine, or ARM without x64 emulation), the installer still fails closed but explains this is not a failed signature and does not suggest winget install sigstore.cosign (same amd64 build). TRACEBLOC_ALLOW_UNVERIFIED still allows checksum-only proceed with warnings.

Pester coverage locks the amd64 asset name, executability probe, error wording, and no-verify path when cosign won’t run.

Reviewed by Cursor Bugbot for commit 648deec. Bugbot is set up for automated code reviews on this repo. Configure here.

…m64 asset does not exist
Resolve-Cosign built `cosign-windows-$arch.exe`, which on Windows-on-ARM asks
for `cosign-windows-arm64.exe`. Sigstore has never published that file — not at
the pinned v2.4.1, not at v3.1.3, not at any release. `cosign-windows-amd64.exe`
is the only Windows asset there is.
So the download 404s, Resolve-Cosign returns $null, and the install fails
closed. Safe direction, accidental reason: the user sees a cosign download
failure that reads as a network blip, and retrying can never succeed. Nobody
noticed because Windows-on-ARM is rare in the user base — low incidence, total
blockage, no signal.
Both architectures now fetch the amd64 build and run it under Windows-on-ARM's
x64 emulation. That costs nothing that matters: cosign verifies a signature over
BYTES, so the instruction set it was compiled for cannot change the verdict, and
the artifact handed to it is still the native arm64 binary. The bootstrapped
cosign is checksum-verified before use exactly as on amd64, so the trust chain
is unchanged.
The alternative — fail closed with a `winget install sigstore.cosign` hint —
routes the user to do the same thing by hand, since winget's cosign is also
amd64-only. Same end state, more friction, and one more chance to reach for
TRACEBLOC_ALLOW_UNVERIFIED, which is the outcome we least want.
Second half: Invoke-CosignVerifyBlob fails closed on a binary that cannot
execute, but reported it IDENTICALLY to a signature that did not verify. On an
older Windows 10 on ARM without x64 emulation that tells a user their download
may be tampered with, when the truth is that the verifier will not start. Those
warrant opposite reactions and only one of them is a security event. A
`cosign version` probe after resolution separates them, keeps the fail-closed,
and names the real cause plus a remedy that works.
Both halves are mutation-real: restoring `$arch` fails the asset test; dropping
the probe fails all four behavioural tests. 680 Pester green, make check green.
CI cannot exercise the ARM path — GitHub's Windows runners are x64 — so the
coverage is source-level for the arch map and behavioural for the probe, and the
comment in Resolve-Cosign is load-bearing.
Closes#734
Refs tracebloc/backend#2078
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LukasWodkaLukasWodka self-assigned this Aug 17, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 099ec15. Configure here.

Comment threadscripts/install.ps1 Outdated
saadqbal
saadqbal previously approved these changes Aug 17, 2026

@saadqbalsaadqbal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, careful PR. cosign-windows-amd64.exe is the right (and only) Windows asset; the switch still guards unknown arches, the checksum step is untouched, and install.sh/Linux-arm64 aren't in scope. Nice touch adding Test-CosignRuns so a non-executable cosign (Win10-on-ARM, no x64 emulation) fails closed with the true reason instead of masquerading as a bad signature. Tests are non-vacuous and green (40/40 on Pester 5.7.1). 👍

…d that just failed
Bugbot, and it is my error: I noted earlier in this very investigation that
winget's cosign is amd64-only, then wrote a remedy that depends on it.
It is wrong in this branch by construction. We already HAVE a cosign here — it
will not start. Installing another copy of the same amd64 build, the only one
sigstore and winget publish, reproduces the failure exactly. A remedy that cannot
clear the error is worse than none: it costs a round trip and teaches the user
the message is noise.
The message now names the two things that actually cause it — security software
quarantining the downloaded cosign.exe, and missing x64 emulation on
Windows-on-ARM (Windows 11 has it, Windows 10 on ARM may not) — and says plainly
that no native arm64 cosign exists to fall back on, so one would have to be built
from source. Still fails closed; the TRACEBLOC_ALLOW_UNVERIFIED escape is
unchanged.
Guard added: re-introducing 'winget' into that message reddens the test. It
captures the thrown message and asserts on it — `Should -Not -Throw
-ExpectedMessage` asserts no throw AT ALL, so the first draft would have passed
for the wrong reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LukasWodka
LukasWodka merged commit 5ffd6c5 into developAug 17, 2026
37 checks passed
@LukasWodka
LukasWodka deleted the fix/cosign-windows-arm64-asset branch August 17, 2026 12:55
LukasWodka added a commit that referenced this pull request Aug 17, 2026
`develop` gained #735 (amd64 cosign bootstrap on Windows-on-ARM) and #736
(SS3 escape stripping), both of which touch `scripts/lib/common.sh` — the file
this branch also edits, since the binfmt probe moved there so both arch gates
read one probe.
`common.sh` and `install-client-helm.bats` auto-merged. `manifest.sha256` was
the only conflict and was REGENERATED with `scripts/gen-manifest.sh` rather
than hand-merged: the manifest is a derived artifact, and a hand-resolved one
records hashes for a tree that never existed. Re-running the generator now
produces no diff, so the committed manifest matches the merged tree.
Verified on the merged result, not on either side: the full bats suite exits 0
with 0 failures (1119+ tests), so #739's `_pf_arch` / `_assert_engine_runs_on_this_arch`
changes still hold against develop's `common.sh`.
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

/fr-pass

Best-effort functional review passed (triage: merged clean, non-interactive; behavioral evidence limited while e2e journey is red — backend#2206). Advancing to Ready for prod.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The Windows cosign bootstrap requests an arm64 asset sigstore has never published — Windows-on-ARM installs are permanently blocked

2 participants

@LukasWodka@saadqbal