P3-7: Auto-update — appcast, EdDSA signing, Sparkle and WinSparkle #36

Open
opened 2026-09-10 17:44:46 +00:00 by Cordy · 0 comments
Owner

Depends on P3-6 and P0-5 (the EdDSA key must exist).

Why this is v1 scope

Sync bugs are data-integrity bugs. A fix shipped in month three otherwise sits unapplied on user machines for years, and every version ever released stays in the support matrix permanently. Retrofitting an updater is also harder than building it in, because the old client is the one that must be capable of updating itself.

The convenient fact

Sparkle (macOS) and WinSparkle (Windows) use the same design — the same RSS-based appcast format and the same EdDSA/Ed25519 signatures, with compatible tooling. The signature rides in the sparkle:edSignature attribute of the appcast enclosure. One publishing pipeline and one signing key serve both platforms.

On Linux, ship no updater at all — the package manager, Flatpak or the AppImage updater owns it. Do not invent a fourth mechanism.

Files

  • Create: internal/update/update.go, internal/update/appcast.go, build/appcast/generate.go, internal/update/update_test.go

Security requirements — non-negotiable

  • Verify the EdDSA signature independently of TLS. A compromised web server or CDN must not be able to push a malicious build. TLS authenticates the transport; the signature authenticates the artifact. Both are needed.
  • The EdDSA private key lives offline (P0-5) and is separate from the code-signing certificate. Compromising the website must not be sufficient to ship code. If both live in the same place, this property is gone.
  • Downgrade protection: refuse to install a version older than the running one.
  • Verify before executing, always — never run the downloaded artifact to discover whether it was valid.

Policy control

Auto-update must be disableable via policy (P3-1 autoUpdateEnabled). Managed fleets update through Intune or SCCM and will not accept an application that updates itself behind the administrator's back. Default on for unmanaged installs; honour policy where set.

Publishing pipeline

  • A build step that generates appcast.xml from release artifacts, signs each enclosure with the EdDSA key, and publishes to swisscairn.ch.
  • The signing step must not run in CI with the key present in the environment. Either sign locally and upload, or use a signing service. A key in CI env vars is a key in every CI log waiting to happen.

Steps

  • Implement appcast generation and signing.
  • Integrate Sparkle on macOS and WinSparkle on Windows.
  • Write failing tests: a tampered artifact is rejected; an unsigned artifact is rejected; a downgrade is refused; a policy-disabled client never contacts the update URL at all.
  • Test a real update end to end: install version N, publish N+1, confirm it updates and relaunches cleanly.
  • Commit: git commit -s -m "feat(update): signed auto-update via Sparkle and WinSparkle"

Acceptance criteria

  • A tampered or unsigned artifact is refused, verified by test.
  • Downgrades are refused.
  • With autoUpdateEnabled=false by policy, zero network requests go to the update URL.
  • The EdDSA private key is never present in CI.
Depends on P3-6 and P0-5 (the EdDSA key must exist). ## Why this is v1 scope Sync bugs are **data-integrity** bugs. A fix shipped in month three otherwise sits unapplied on user machines for years, and every version ever released stays in the support matrix permanently. Retrofitting an updater is also harder than building it in, because the *old* client is the one that must be capable of updating itself. ## The convenient fact **Sparkle (macOS) and WinSparkle (Windows) use the same design** — the same RSS-based appcast format and the same EdDSA/Ed25519 signatures, with compatible tooling. The signature rides in the `sparkle:edSignature` attribute of the appcast enclosure. **One publishing pipeline and one signing key serve both platforms.** On **Linux, ship no updater at all** — the package manager, Flatpak or the AppImage updater owns it. Do not invent a fourth mechanism. ## Files - Create: `internal/update/update.go`, `internal/update/appcast.go`, `build/appcast/generate.go`, `internal/update/update_test.go` ## Security requirements — non-negotiable - **Verify the EdDSA signature independently of TLS.** A compromised web server or CDN must not be able to push a malicious build. TLS authenticates the transport; the signature authenticates the artifact. Both are needed. - **The EdDSA private key lives offline** (P0-5) and is **separate from the code-signing certificate**. Compromising the website must not be sufficient to ship code. If both live in the same place, this property is gone. - **Downgrade protection:** refuse to install a version older than the running one. - **Verify before executing**, always — never run the downloaded artifact to discover whether it was valid. ## Policy control Auto-update must be disableable via policy (P3-1 `autoUpdateEnabled`). Managed fleets update through Intune or SCCM and will not accept an application that updates itself behind the administrator's back. Default on for unmanaged installs; honour policy where set. ## Publishing pipeline - [ ] A build step that generates `appcast.xml` from release artifacts, signs each enclosure with the EdDSA key, and publishes to `swisscairn.ch`. - [ ] **The signing step must not run in CI with the key present in the environment.** Either sign locally and upload, or use a signing service. A key in CI env vars is a key in every CI log waiting to happen. ## Steps - [ ] Implement appcast generation and signing. - [ ] Integrate Sparkle on macOS and WinSparkle on Windows. - [ ] Write failing tests: a **tampered artifact is rejected**; an **unsigned artifact is rejected**; a **downgrade is refused**; a policy-disabled client never contacts the update URL at all. - [ ] Test a real update end to end: install version N, publish N+1, confirm it updates and relaunches cleanly. - [ ] Commit: `git commit -s -m "feat(update): signed auto-update via Sparkle and WinSparkle"` ## Acceptance criteria - A tampered or unsigned artifact is refused, verified by test. - Downgrades are refused. - With `autoUpdateEnabled=false` by policy, **zero** network requests go to the update URL. - The EdDSA private key is never present in CI.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/cairn-desktop#36
No description provided.