From 482e06b0dfab8d82d4207318dd7e3a1a7f73599d Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 09:35:57 +1000 Subject: [PATCH 1/6] chore: add build provenance attestation and SBOM generation --- .github/workflows/release.yml | 38 ++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3e43fe..b0f658d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,13 +16,13 @@ jobs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} steps: - - uses: actions/create-github-app-token@v2 + - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 id: app-token with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: googleapis/release-please-action@v4 + - uses: googleapis/release-please-action@8b8fd2cc23b2e18957157a9d923d75aa0c6f6ad5 # v4 id: release with: token: ${{ steps.app-token.outputs.token }} @@ -31,18 +31,46 @@ jobs: needs: release-please if: ${{ needs.release-please.outputs.release_created }} runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + attestations: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - name: Cache cargo registry - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 - name: Run tests before publish run: cargo test --all-features + - name: Package crate + run: cargo package + + - name: Attest Build Provenance + uses: actions/attest-build-provenance@96b4a1ef7235a096b17240c259729fdd70c83d45 # v2 + with: + subject-path: target/package/*.crate + continue-on-error: true + + - name: Install cargo-sbom + run: cargo install cargo-sbom --locked + continue-on-error: true + + - name: Generate SBOM + run: cargo sbom --output-format cyclonedx_json_v1_6 > sbom.cdx.json + continue-on-error: true + + - name: Attest SBOM + uses: actions/attest-sbom@10926c72720ffc3f7b666661c8e55b1344e2a365 # v2 + with: + subject-path: target/package/*.crate + sbom-path: sbom.cdx.json + continue-on-error: true + - name: Publish to crates.io run: cargo publish env: From 8a7932c6ffa35524d2107a5f1ac47d5cd801fa28 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 09:46:29 +1000 Subject: [PATCH 2/6] chore: migrate from Dependabot to Renovate --- .github/dependabot.yml | 22 ---------------------- renovate.json | 4 ++++ 2 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 .github/dependabot.yml create mode 100644 renovate.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f1084db..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: 2 -updates: - # Cargo dependencies - - package-ecosystem: cargo - directory: / - schedule: - interval: weekly - commit-message: - prefix: "deps" - groups: - rust-dependencies: - patterns: - - "*" - open-pull-requests-limit: 5 - - # GitHub Actions - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - commit-message: - prefix: "ci" diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..262f53f --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>cachekit-io/renovate-config"] +} From 77db4554970f2a4efa7039b07e38e0b561117cba Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 09:52:13 +1000 Subject: [PATCH 3/6] chore: add weekly attestation health check --- .github/workflows/attestation-check.yml | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/attestation-check.yml diff --git a/.github/workflows/attestation-check.yml b/.github/workflows/attestation-check.yml new file mode 100644 index 0000000..00754e4 --- /dev/null +++ b/.github/workflows/attestation-check.yml @@ -0,0 +1,50 @@ +name: Attestation Health Check + +on: + schedule: + - cron: '23 3 * * 1' # Weekly Monday 3:23am UTC + workflow_dispatch: + +jobs: + verify: + name: Verify Latest Release Attestations + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - name: Get latest release tag + id: release + run: | + TAG=$(gh release list --repo ${{ github.repository }} --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || echo "") + if [ -z "$TAG" ]; then + echo "No releases found, skipping" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify attestations + if: steps.release.outputs.skip != 'true' + run: | + echo "Verifying attestations for ${{ steps.release.outputs.tag }}" + gh attestation list --repo ${{ github.repository }} --limit 1 | grep -q "." || { + echo "::error::No attestations found for latest release" + exit 1 + } + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Open issue on failure + if: failure() + run: | + gh issue create \ + --repo ${{ github.repository }} \ + --title "Attestation verification failed for ${{ steps.release.outputs.tag }}" \ + --body "Weekly attestation health check failed. Verify that the release workflow produced valid attestations." \ + --label "bug" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 4ca129d99c30eb84b70ef50c677cad48a8a17999 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 11:57:54 +1000 Subject: [PATCH 4/6] chore: SHA-pin all actions and switch to self-hosted runners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pin all action refs to full commit SHAs (org policy: sha_pinning_required) - Switch ubuntu-based jobs from ubuntu-latest to cachekit (ARC self-hosted) - Keep macos-latest and windows-latest on GitHub-hosted (no self-hosted equivalent) - Fix stale MSRV guard (1.80 → 1.85) in ci.yml components conditional - Update Swatinem/rust-cache SHA in release.yml to match current v2 --- .github/workflows/attestation-check.yml | 2 +- .github/workflows/ci.yml | 25 +++++++----- .github/workflows/codeql.yml | 10 ++--- .github/workflows/release.yml | 6 +-- .github/workflows/security.yml | 52 ++++++++++++------------- 5 files changed, 50 insertions(+), 45 deletions(-) diff --git a/.github/workflows/attestation-check.yml b/.github/workflows/attestation-check.yml index 00754e4..1a0b049 100644 --- a/.github/workflows/attestation-check.yml +++ b/.github/workflows/attestation-check.yml @@ -8,7 +8,7 @@ on: jobs: verify: name: Verify Latest Release Attestations - runs-on: ubuntu-latest + runs-on: cachekit permissions: contents: read issues: write diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78fae2a..84b5730 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,37 +11,42 @@ concurrency: jobs: test: name: ${{ matrix.rust }} / ${{ matrix.os }} - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.runner }} continue-on-error: ${{ matrix.rust == 'beta' }} strategy: fail-fast: false matrix: - # Full OS matrix for stable only; MSRV and beta on ubuntu only + # Full OS matrix for stable only; MSRV and beta on self-hosted only include: # MSRV - ensures we don't use newer Rust features - rust: "1.85" os: ubuntu-latest + runner: cachekit # Stable - primary target, all platforms - rust: stable os: ubuntu-latest + runner: cachekit - rust: stable os: macos-latest + runner: macos-latest - rust: stable os: windows-latest + runner: windows-latest # Beta - early warning (allowed to fail) - rust: beta os: ubuntu-latest + runner: cachekit steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master with: toolchain: ${{ matrix.rust }} - components: ${{ matrix.rust != '1.80' && 'rustfmt, clippy' || '' }} + components: ${{ matrix.rust != '1.85' && 'rustfmt, clippy' || '' }} - name: Cache cargo registry - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: cache-all-crates: true @@ -62,15 +67,15 @@ jobs: run: cargo test --features ffi security: - runs-on: ubuntu-latest + runs-on: cachekit steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - name: Cache cargo registry - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: cache-all-crates: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8229a7c..6ac6a19 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,20 +16,20 @@ concurrency: jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: cachekit permissions: actions: read contents: read security-events: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 with: languages: cpp # Analyze generated C FFI headers and cross-language test code @@ -45,6 +45,6 @@ jobs: cc -std=c99 -Wall -Wextra -I../../include -c test_c.c -o test_c.o - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 with: category: "/language:cpp" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b0f658d..ce14fab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ permissions: jobs: release-please: - runs-on: ubuntu-latest + runs-on: cachekit outputs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} @@ -30,7 +30,7 @@ jobs: publish: needs: release-please if: ${{ needs.release-please.outputs.release_created }} - runs-on: ubuntu-latest + runs-on: cachekit permissions: contents: read id-token: write @@ -42,7 +42,7 @@ jobs: uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - name: Cache cargo registry - uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Run tests before publish run: cargo test --all-features diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index e796787..ef3c867 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -21,20 +21,20 @@ env: jobs: fast-security: name: Fast Security Checks - runs-on: ubuntu-latest + runs-on: cachekit if: github.event_name == 'push' || github.event_name == 'pull_request' steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable with: toolchain: "stable" components: clippy - name: Cache Rust dependencies - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | ~/.cargo/registry/index/ @@ -66,7 +66,7 @@ jobs: quick-fuzz: name: Quick Fuzz (Corpus Only) - runs-on: ubuntu-latest + runs-on: cachekit if: github.event_name == 'push' || github.event_name == 'pull_request' strategy: fail-fast: false @@ -90,13 +90,13 @@ jobs: - key_derivation steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly + uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly - name: Cache Rust dependencies - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | ~/.cargo/registry/index/ @@ -121,7 +121,7 @@ jobs: deep-fuzz: name: Deep Fuzzing (8 hours) - runs-on: ubuntu-latest + runs-on: cachekit if: github.event_name == 'schedule' strategy: fail-fast: false @@ -145,13 +145,13 @@ jobs: - key_derivation steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly + uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly - name: Cache Rust dependencies - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | ~/.cargo/registry/index/ @@ -176,7 +176,7 @@ jobs: - name: Upload crash artifacts if: always() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: fuzz-crashes-${{ matrix.target }} path: fuzz/artifacts/${{ matrix.target }}/ @@ -184,19 +184,19 @@ jobs: kani: name: Kani Formal Verification - runs-on: ubuntu-latest + runs-on: cachekit if: github.event_name == 'schedule' steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master with: toolchain: "1.85" - name: Cache Rust dependencies - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | ~/.cargo/registry/index/ @@ -219,19 +219,19 @@ jobs: cargo-vet: name: Cargo Vet (Supply Chain) - runs-on: ubuntu-latest + runs-on: cachekit if: github.event_name == 'schedule' steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master with: toolchain: "1.85" - name: Cache Rust dependencies - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | ~/.cargo/registry/index/ @@ -251,19 +251,19 @@ jobs: sbom: name: Generate SBOM - runs-on: ubuntu-latest + runs-on: cachekit if: github.event_name == 'release' steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master with: toolchain: "1.85" - name: Cache Rust dependencies - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | ~/.cargo/registry/index/ @@ -282,7 +282,7 @@ jobs: run: cargo sbom > cachekit-core-sbom.json - name: Upload SBOM as release asset - uses: actions/upload-release-asset@v1 + uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From 6cd3935138833e7e950c26a40d30ac62e7a6b22a Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 12:24:53 +1000 Subject: [PATCH 5/6] chore: fix cargo-deny bans (winnow skip, remove stale entries) - Add winnow@0.7 skip (duplicate via toml/toml_parser in cbindgen) - Remove stale getrandom@0.3, rand_core@0.6, libc@0.2 skips --- deny.toml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/deny.toml b/deny.toml index 17ea1fd..d897dce 100644 --- a/deny.toml +++ b/deny.toml @@ -70,16 +70,11 @@ deny = [] # Skip specific dependencies from multiple-version checks # These are transitive dependencies where version duplication is unavoidable skip = [ - # getrandom has 3 major versions in the dep tree: - # 0.2.x via aes-gcm/crypto-common (encryption) - # 0.3.x via proptest (dev-dependency) - # 0.4.x via tempfile/cbindgen (build-dependency) + # getrandom 0.2.x (aes-gcm crypto chain) vs 0.4.x (tempfile/cbindgen build-dep) { crate = "getrandom@0.2", reason = "Transitive via aes-gcm crypto chain" }, - { crate = "getrandom@0.3", reason = "Transitive via proptest (dev-dependency)" }, - # rand_core duplication from aes-gcm (0.6.x) vs proptest (0.9.x) - { crate = "rand_core@0.6", reason = "Transitive via aes-gcm crypto chain" }, - # libc duplication unavoidable (getrandom versions pull different libc) - { crate = "libc@0.2", reason = "Transitive via multiple getrandom versions" }, + # winnow 0.7.x vs 1.x — both pulled by toml@0.9 (direct + via toml_parser) + # through cbindgen build-dependency; internal split, can't resolve + { crate = "winnow@0.7", reason = "Transitive via toml/cbindgen (build-dependency)" }, ] # Skip crate trees entirely (e.g., frequently-updated foundational crates) From 500f1db71d59897f817be0bfc7a369876c7d5d89 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 25 Apr 2026 12:32:14 +1000 Subject: [PATCH 6/6] fix: harden attestation verification and fix SBOM generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit release.yml: - Remove continue-on-error from attestation steps (fail release if attestation fails, don't silently ship without supply chain proof) - Fix cargo-sbom format flag: cyclonedx_json_v1_6 → cyclone_dx_json_1_6 - Add non-empty validation for generated SBOM file attestation-check.yml: - Verify against actual release artifact (gh attestation verify) instead of loose repo-level list check - Assert both provenance and SBOM predicate types are present - Graceful fallback when no .crate asset is downloadable --- .github/workflows/attestation-check.yml | 70 ++++++++++++++++++++++--- .github/workflows/release.yml | 8 ++- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/.github/workflows/attestation-check.yml b/.github/workflows/attestation-check.yml index 1a0b049..21d162a 100644 --- a/.github/workflows/attestation-check.yml +++ b/.github/workflows/attestation-check.yml @@ -13,6 +13,8 @@ jobs: contents: read issues: write steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Get latest release tag id: release run: | @@ -27,14 +29,68 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Verify attestations + - name: Download release asset if: steps.release.outputs.skip != 'true' + id: asset run: | - echo "Verifying attestations for ${{ steps.release.outputs.tag }}" - gh attestation list --repo ${{ github.repository }} --limit 1 | grep -q "." || { - echo "::error::No attestations found for latest release" - exit 1 - } + TAG="${{ steps.release.outputs.tag }}" + mkdir -p /tmp/release-assets + gh release download "$TAG" --repo ${{ github.repository }} \ + --pattern "*.crate" --dir /tmp/release-assets 2>/dev/null || true + CRATE=$(find /tmp/release-assets -name '*.crate' -print -quit) + if [ -z "$CRATE" ]; then + echo "::warning::No .crate asset found for $TAG, checking repo-level attestations" + echo "has_asset=false" >> "$GITHUB_OUTPUT" + else + echo "asset=$CRATE" >> "$GITHUB_OUTPUT" + echo "has_asset=true" >> "$GITHUB_OUTPUT" + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify provenance attestation + if: steps.release.outputs.skip != 'true' + run: | + TAG="${{ steps.release.outputs.tag }}" + echo "Verifying provenance attestation for $TAG" + if [ "${{ steps.asset.outputs.has_asset }}" = "true" ]; then + gh attestation verify "${{ steps.asset.outputs.asset }}" \ + --repo ${{ github.repository }} \ + --predicate-type https://slsa.dev/provenance/v1 + else + # No downloadable .crate asset — verify attestations exist for the repo + PROVENANCE=$(gh attestation list --repo ${{ github.repository }} \ + --predicate-type https://slsa.dev/provenance/v1 --limit 1 --jq 'length') + if [ "$PROVENANCE" = "0" ] || [ -z "$PROVENANCE" ]; then + echo "::error::No provenance attestation found" + exit 1 + fi + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify SBOM attestation + if: steps.release.outputs.skip != 'true' + run: | + TAG="${{ steps.release.outputs.tag }}" + echo "Verifying SBOM attestation for $TAG" + if [ "${{ steps.asset.outputs.has_asset }}" = "true" ]; then + gh attestation verify "${{ steps.asset.outputs.asset }}" \ + --repo ${{ github.repository }} \ + --predicate-type https://spdx.dev/Document/v2.3 || \ + gh attestation verify "${{ steps.asset.outputs.asset }}" \ + --repo ${{ github.repository }} \ + --predicate-type https://cyclonedx.org/bom/v1.6 + else + SBOM=$(gh attestation list --repo ${{ github.repository }} \ + --predicate-type https://cyclonedx.org/bom/v1.6 --limit 1 --jq 'length' 2>/dev/null || echo "0") + SPDX=$(gh attestation list --repo ${{ github.repository }} \ + --predicate-type https://spdx.dev/Document/v2.3 --limit 1 --jq 'length' 2>/dev/null || echo "0") + if [ "$SBOM" = "0" ] && [ "$SPDX" = "0" ]; then + echo "::error::No SBOM attestation found (checked CycloneDX and SPDX)" + exit 1 + fi + fi env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -44,7 +100,7 @@ jobs: gh issue create \ --repo ${{ github.repository }} \ --title "Attestation verification failed for ${{ steps.release.outputs.tag }}" \ - --body "Weekly attestation health check failed. Verify that the release workflow produced valid attestations." \ + --body "Weekly attestation health check failed. Verify that the release workflow produced valid provenance and SBOM attestations." \ --label "bug" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce14fab..d2f5331 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,22 +54,20 @@ jobs: uses: actions/attest-build-provenance@96b4a1ef7235a096b17240c259729fdd70c83d45 # v2 with: subject-path: target/package/*.crate - continue-on-error: true - name: Install cargo-sbom run: cargo install cargo-sbom --locked - continue-on-error: true - name: Generate SBOM - run: cargo sbom --output-format cyclonedx_json_v1_6 > sbom.cdx.json - continue-on-error: true + run: | + cargo sbom --output-format cyclone_dx_json_1_6 > sbom.cdx.json + test -s sbom.cdx.json || { echo "::error::SBOM file is empty"; exit 1; } - name: Attest SBOM uses: actions/attest-sbom@10926c72720ffc3f7b666661c8e55b1344e2a365 # v2 with: subject-path: target/package/*.crate sbom-path: sbom.cdx.json - continue-on-error: true - name: Publish to crates.io run: cargo publish