diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0d9ff4c..3713113 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,6 +45,17 @@ jobs: } Write-Host "Cargo version $cargoVersion matches tag $tag" + # An exe without the updater public key can never verify, and so never + # install, a later update; every install of it would be stranded. + - name: Refuse an empty updater public key + shell: pwsh + run: | + $pubkey = (Get-Content tauri.conf.json -Raw | ConvertFrom-Json).plugins.updater.pubkey + if ([string]::IsNullOrWhiteSpace($pubkey)) { + Write-Error "plugins.updater.pubkey in tauri.conf.json is empty: put the updater public key there before releasing." + exit 1 + } + - name: Build release run: cargo build --release --locked diff --git a/plans/261021-1028-tauri-v2-port/phase-09-updater-migration.md b/plans/261021-1028-tauri-v2-port/phase-09-updater-migration.md index 7d830f3..2e86e91 100644 --- a/plans/261021-1028-tauri-v2-port/phase-09-updater-migration.md +++ b/plans/261021-1028-tauri-v2-port/phase-09-updater-migration.md @@ -114,7 +114,7 @@ self-replaces on Windows and runs the plugin's install on the AppImage. - **Step 3 (signature):** in `tauri-plugin-updater` 2.14.0, `Update::download` ends with `self.verify(&buffer)?`, which runs `minisign_verify::PublicKey::verify` against the configured `pubkey` before any bytes are returned, and `Update::install` verifies again. The plugin's own offline tests (`verifies_cached_bytes_and_rejects_tampering`, `install_rejects_bytes_that_do_not_match_the_signature`, plus the signed-version ones) were run on this host against the pinned source and pass: tampered bytes, a tampered signature, an invalid signature and a different key are all rejected. No separate `minisign-verify` call is needed. - **Step 4 (timeout):** the builder timeout does **not** reach the download. `Updater::update_from_release` builds every `Update` with `timeout: None`, so `download()` would run without any limit. Following the phase, apply drops the second builder and the second `check()`: it downloads the `Update` the last check found, after setting its public `timeout` field to 10 min (`update::DOWNLOAD_TIMEOUT`). The found update is kept in `Runtime::pending_update`, outside the state lock, as v0.5.0 kept its release. -- **Public key:** the agent could not generate the signing key (key generation is the user action in step 1, and it was refused here as credential handling). `plugins.updater.pubkey` is therefore an empty string, which the plugin accepts at registration: checks work, and a download fails signature verification, so nothing unverified is ever installed. `build.rs` refuses to build when `TAURI_SIGNING_PRIVATE_KEY` is set and the effective `pubkey` (file or `--config`) is empty, so a signed release cannot ship without it. +- **Public key:** the agent could not generate the signing key (key generation is the user action in step 1, and it was refused here as credential handling). `plugins.updater.pubkey` is therefore an empty string, which the plugin accepts at registration: checks work, and a download fails signature verification, so nothing unverified is ever installed. `build.rs` refuses to build when `TAURI_SIGNING_PRIVATE_KEY` is set and the effective `pubkey` (file or `--config`) is empty, so a signed release cannot ship without it. `release.yml` also refuses to build, before any build step, while `pubkey` is empty, so an unsigned tag build cannot publish an exe that could never verify an update. - **Proxy:** the plugin's reqwest reads env proxies itself, so the parsed system proxy is passed only when no env proxy is set. `Proxy::all` cannot bypass per host, so it is skipped when `github.com` or a GitHub release-asset host is in `ProxyOverride`; reqwest's own system-proxy support then routes those requests. - **Error log:** only apply failures write `update-error.log`, as in v0.5.0; check failures are logged under `--diagnose` only, so a transient network error never overwrites the record of a failed swap. - **Concurrency:** check and apply claim their status under the state lock (`Idle/UpToDate/Available/Failed` to `Checking`, `Available` to `Applying`), so a scheduled check cannot run alongside an apply. A scheduled check that fires during an apply re-arms the schedule instead of being lost. diff --git a/plans/261021-1028-tauri-v2-port/phase-10-ci-release.md b/plans/261021-1028-tauri-v2-port/phase-10-ci-release.md index 75b3d1e..ddc8ab0 100644 --- a/plans/261021-1028-tauri-v2-port/phase-10-ci-release.md +++ b/plans/261021-1028-tauri-v2-port/phase-10-ci-release.md @@ -32,6 +32,10 @@ host has no patchelf and no display. `--config '{"bundle":{"createUpdaterArtifacts":true}}'` where bundles are built, with `TAURI_SIGNING_PRIVATE_KEY` and `_PASSWORD` in the environment. A signing step fails hard when the key variable is empty. + - Keep the step that fails before any build when `plugins.updater.pubkey` in + `tauri.conf.json` is empty. `build.rs` only checks the key when + `TAURI_SIGNING_PRIVATE_KEY` is in the build environment, so it does not + cover a build step that runs without it. - **win-x64** job (`windows-latest`): - `npm run tauri build -- --no-bundle`. - `npx tauri signer sign target/release/claude-code-usage-bubble.exe`. This