Skip to content

fix(webui): verify mac bundled uv via codesign instead of a post-codesign SHA pin#1926

Merged
itomek merged 1 commit into
mainfrom
fix/uv-mac-arm64-pin-refresh
Jul 6, 2026
Merged

fix(webui): verify mac bundled uv via codesign instead of a post-codesign SHA pin#1926
itomek merged 1 commit into
mainfrom
fix/uv-mac-arm64-pin-refresh

Conversation

@itomek

@itomek itomek commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

The "DMG structural smoke" check fails on roughly half of CI runs — including Dependabot PRs #1861 and #1886 — and, worse, any shipped DMG built on the "other" runner image would hard-fail ensureUv() on a user's first launch. Root cause: BUNDLED_UV_SHA256["mac-arm64"] pins the POST-codesign digest of the bundled uv, but every CI build ad-hoc-signs it (identity=-), and ad-hoc codesign output differs by the Xcode/codesign toolchain in the floating macos-latest image. Observed bimodal digests on byte-identical source: macos-26-arm64 images → 7ad94b65…, macos-15-arm646099aa8c…. No fixed pin can survive an image rollover.

This replaces the mac pin with codesign --verify --strict in both ensureUv() (runtime: bundled resource, install, and warm-cache paths) and the dmg-structural-smoke helper — deterministic across runner images, still fail-loud with the same actionable InstallError on corruption or tampering. linux-x64 / win-x64 keep their SHA256 pins (deterministic there — no post-build re-signing). Supply-chain integrity for the mac binary remains enforced at build time by the existing upstream-tarball SHA256 verification in build-installers.yml.

Reviewer tradeoff to weigh: codesign --verify proves the binary is intact and validly signed, but unlike the old pin it does not tie the warm-cached copy in ~/.gaia/bin to the exact shipped bytes (a local attacker able to replace the file could re-sign ad-hoc). If that matters, a follow-up could record the install-time digest and re-verify it on cache reuse (TOFU) — kept out of scope here to fix the release-breaking nondeterminism first.

Test plan

  • Empirical codesign behavior: ad-hoc-signed binary passes --verify --strict; byte-flipped binary fails with "invalid signature" (scratch harness against the real ensureUv() code path — first-install, warm-cache reuse, and tamper/fail-loud paths all verified)
  • node --check on all touched files; tests/electron jest suite 626/627 (the 1 failure is the pre-existing title assertion, fixed separately in fix(tests): match chat-app title assertion to renamed Agent UI title #1927)
  • "DMG structural smoke" green on this PR's build-installers run — this exercises the new verification against a real CI-built DMG, on whichever runner image the job lands

@itomek

itomek commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Update: the pin-refresh approach is insufficient. This PR's own dmg-structural-smoke run produced the ORIGINAL digest (6099aa8…) as "actual" — while #1861 and #1886 both produced 7ad94b65… on identical vendored uv. The post-codesign digest is bimodal across runs (mixed macOS runner-image rollout), so no fixed pin can be stable — and worse, a shipped DMG whose signing output mismatches the baked-in pin would hard-fail ensureUv() on user machines at first launch. Reworking this PR to verify the mac binary deterministically (signature validity at runtime + the existing pre-codesign upstream-tarball pin at build time) instead of pinning a post-codesign digest; the branch will be force-pushed with the redesign.

The BUNDLED_UV_SHA256["mac-arm64"] pin captured a post-codesign digest,
but electron-builder ad-hoc-signs the bundled uv on every CI build (no
Developer ID cert configured), and ad-hoc codesign output depends on the
codesign/Xcode toolchain baked into the GitHub-hosted macos-latest runner
image. That image floats between macos-15-arm64 and macos-26-arm64,
producing two different digests for byte-identical source — confirmed
across dmg-structural-smoke runs on PRs #1861, #1886, and #1926. A fixed
pin is therefore not deterministic on macOS: it fails roughly half of CI
runs, and a shipped DMG whose signing output mismatches the baked-in pin
would hard-fail ensureUv() on a user's first launch.

Replace the fixed digest with `codesign --verify --strict` in both
ensureUv() (runtime) and the dmg-structural-smoke helper (CI), matching
the check to what's actually deterministic: the signature's structural
validity, not its exact bytes. Ad-hoc signatures pass this check;
tampered or corrupted binaries still fail it, preserving the fail-loud
guarantee. linux-x64/win-x64 keep their existing SHA256 pins, which are
genuinely deterministic there (no post-build re-signing).
@itomek itomek force-pushed the fix/uv-mac-arm64-pin-refresh branch from a6f8b47 to f70a493 Compare July 6, 2026 17:10
@github-actions github-actions Bot added devops DevOps/infrastructure changes tests Test changes labels Jul 6, 2026
@itomek itomek changed the title fix(webui): refresh mac-arm64 uv post-codesign SHA256 pin fix(webui): verify mac bundled uv via codesign instead of a post-codesign SHA pin Jul 6, 2026
@itomek itomek self-assigned this Jul 6, 2026
@itomek itomek marked this pull request as ready for review July 6, 2026 17:26
@itomek itomek requested a review from kovtcharov-amd as a code owner July 6, 2026 17:26
@itomek itomek enabled auto-merge July 6, 2026 17:26
@itomek itomek added this pull request to the merge queue Jul 6, 2026
Merged via the queue into main with commit dc2d42f Jul 6, 2026
79 of 81 checks passed
@itomek itomek deleted the fix/uv-mac-arm64-pin-refresh branch July 6, 2026 17:28
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Verdict: Approve

This swaps the mac-arm64 bundled-uv integrity check from a post-codesign SHA256 pin to codesign --verify --strict, fixing the release-breaking nondeterminism where ad-hoc codesign output differs across floating macos-latest runner images (so ~half of DMG smoke runs failed and a shipped DMG could brick first launch). The change is correct, stays fail-loud, keeps the SHA pins for linux/win, and the tests + CI comments were updated to match. Build-time supply-chain integrity for the mac binary is preserved — the upstream tarball SHA is still verified in build-installers.yml before bundling.

One thing to be aware of (the author already flagged it in the description, not a blocker): on macOS the warm cache in ~/.gaia/bin is no longer tied to the exact shipped bytes — a local attacker who can write there could substitute a re-signed ad-hoc binary. That's a conscious tradeoff to unbreak releases, with a TOFU follow-up noted. Reasonable to defer.

Nothing here needs to block the merge.

🔍 Technical details

Correctness — verified, no blocking issues

  • The installBundledUv restructure is sound: the early guard now reads if (!isDarwin && (!expected || expected.startsWith("<"))), so the non-darwin branch is guaranteed a real pin, and the old silent else log("...not be verified...") fallback is gone — this is stricter than before, in line with the no-silent-fallbacks rule (backend-installer.cjs:1046, :971).
  • chmod moved inside both branches; darwin chmods before codesignVerify (comment explains why), non-darwin chmods after the hash check. Rename path after the if/else is intact (backend-installer.cjs:1024-1075).
  • codesignVerify fails loud on a missing/broken codesign too: spawnSync ENOENT → status null → ok=falseInstallError. No swallowed error (backend-installer.cjs:945).
  • Fast-path branching in ensureUv is correct for all platform keys, including platformKey === null (both conditions false → falls through to bundled install) (backend-installer.cjs:1131-1141).
  • Build-time integrity confirmed intact: build-installers.yml:289-297 still verifies the upstream mac tarball against d548dffc… before extract/bundle. The description's claim holds.

🟢 Minor (nits, non-blocking)

  • The darwin-branch InstallError suggestion says "The AppImage/installer may be corrupt" — "AppImage" is Linux-specific terminology in a mac-only code path. Small wording snag for a mac user reading the error.

          `Bundled uv failed code signature verification (codesign --verify --strict): ${output || "no output"}`,
          {
            stage: STAGES.ENSURE_UV,
            suggestion:
              "The installer may be corrupt. Re-download from https://amd-gaia.ai and try again.",
          }
    
  • docs/releases/v0.17.6.mdx:50 still describes the mac binary as "SHA-pinned" — it's a historical release note so leaving it is fine, just noting it no longer reflects current behavior.

Strengths

  • Root-caused precisely (bimodal digests 7ad94b65… vs 6099aa8c… on byte-identical source across runner images) and fixed the actual determinism problem rather than papering over the smoke failure.
  • Fail-loud preserved end-to-end — every path throws an actionable InstallError on tamper/corruption; the risky else "not verified" fallback was removed, not added to.
  • Runtime check and the dmg/appimage smoke helpers were updated together so the test asserts exactly what ensureUv() enforces — no drift between them.
  • Security tradeoff disclosed transparently in the PR description with a concrete follow-up, rather than buried.

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

Labels

devops DevOps/infrastructure changes tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants