From 781a29799387ef838d3af56abc1b2a9981258d96 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 29 Jun 2026 10:14:21 +0800 Subject: [PATCH] ci(release): attest release and nightly build artifacts (#4851) Add actions/attest-build-provenance to both build workflows so every binary is attested in the same job that builds it, the only point where provenance meaningfully proves an artifact was built from source rather than uploaded by hand. release.yml (build-tauri): grant id-token and attestations write permissions, then attest the desktop bundles via the tauri-action artifactPaths output, the Android apks, and the Windows portable exe. nightly.yml (build): same permissions plus one step attesting the staged nightly-out binaries. Nightlies ship via download.readest.com, but gh attestation verify is digest based so it verifies them too. Verify a download with: gh attestation verify --repo readest/readest Closes #4848 Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/nightly.yml | 18 ++++++++++++++++++ .github/workflows/release.yml | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f4a03306..9846d3e6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -40,6 +40,12 @@ jobs: build: needs: compute-version + permissions: + contents: read + # Required by actions/attest-build-provenance: id-token mints the Sigstore + # OIDC identity, attestations writes the provenance to the repo's store. + id-token: write + attestations: write strategy: fail-fast: false matrix: @@ -381,6 +387,18 @@ jobs: ;; esac + # Attest the distributable binaries staged in nightly-out (apks, AppImage, + # app.tar.gz, setup/portable exe). gh attestation verify is digest-based, + # so it verifies these against readest/readest even though they ship via + # download.readest.com rather than a GitHub release. The .sig updater + # signatures are excluded — they are not binaries users run. + - name: attest nightly binaries + uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 + with: + subject-path: | + nightly-out/Readest* + !nightly-out/*.sig + - name: upload artifacts + fragment to R2 shell: bash run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e2b6c3c..7ca2eb38 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -122,6 +122,10 @@ jobs: needs: get-release permissions: contents: write + # Required by actions/attest-build-provenance: id-token mints the Sigstore + # OIDC identity, attestations writes the provenance to the repo's store. + id-token: write + attestations: write strategy: fail-fast: false matrix: @@ -267,6 +271,14 @@ jobs: gh release upload ${{ needs.get-release.outputs.release_tag }} $universial_apk.sig --clobber gh release upload ${{ needs.get-release.outputs.release_tag }} $arm64_apk.sig --clobber + - name: attest Android apks + if: matrix.config.release == 'android' + uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 + with: + subject-path: | + apps/readest-app/Readest_${{ needs.get-release.outputs.release_version }}_universal.apk + apps/readest-app/Readest_${{ needs.get-release.outputs.release_version }}_arm64.apk + - name: download and update latest.json for Android release if: matrix.config.release == 'android' env: @@ -309,6 +321,7 @@ jobs: run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage --force - uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0 + id: tauri if: matrix.config.release != 'android' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -332,6 +345,16 @@ jobs: releaseBody: ${{ needs.get-release.outputs.release_note }} args: ${{ matrix.config.args || '' }} + # Attest the freshly built desktop bundles (installers, AppImage, dmg, + # updater archives + their .sig). tauri-action reports their on-disk paths + # as a JSON array; fromJSON('"\n"') yields a real newline to join them into + # the newline-delimited list subject-path expects. + - name: attest desktop bundles + if: matrix.config.release != 'android' && steps.tauri.outputs.artifactPaths != '' + uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 + with: + subject-path: ${{ join(fromJSON(steps.tauri.outputs.artifactPaths), fromJSON('"\n"')) }} + - name: upload release notes to GitHub release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -384,6 +407,15 @@ jobs: echo "Uploading signature to GitHub release" gh release upload ${{ needs.get-release.outputs.release_tag }} $bin_file.sig --clobber + # The portable rebuild above is not produced by tauri-action, so it is not + # covered by the "attest desktop bundles" step; attest it here. Exactly one + # portable exe is staged at the workspace root per Windows leg. + - name: attest Windows portable binary + if: matrix.config.os == 'windows-latest' + uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 + with: + subject-path: Readest_*-portable.exe + - name: download and update latest.json for Windows portable release if: matrix.config.os == 'windows-latest' env: