From 156d08f001bea91f538325b4d7ec0fd079aba53b Mon Sep 17 00:00:00 2001 From: forkwright Date: Fri, 29 May 2026 21:42:00 +0000 Subject: [PATCH] chore(ci): cargo-auditable + SLSA L2 + osv-scanner - Add attestations: write, id-token: write permissions to release.yml - Install cargo-auditable and switch to cargo auditable build / cross auditable build - Add per-binary CycloneDX + SPDX SBOM generation via anchore/sbom-action - Add SLSA L2 binary provenance + SBOM attestation via actions/attest-build-provenance and actions/attest-sbom - Upload .intoto.jsonl attestation bundles as release assets - Create Cross.toml with cargo-auditable pre-build for aarch64 cross-compilation - Add osv-scanner reusable workflow job to security.yml - Create osv-scanner.toml waiver list (empty, matching current advisory state) Closes kanon#507 Closes kanon#508 Gate-Passed: kanon-0.1.5 full --- .github/workflows/release.yml | 74 +++++++++++++++++++++++++++++++--- .github/workflows/security.yml | 11 +++++ Cross.toml | 9 +++++ crates/koinon/src/baseline.rs | 4 +- osv-scanner.toml | 4 ++ 5 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 Cross.toml create mode 100644 osv-scanner.toml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54ad16d..bc8abab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,9 @@ on: tags: ["v*"] permissions: + attestations: write contents: write + id-token: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -51,21 +53,81 @@ jobs: - name: Install cross if: matrix.cross run: cargo install cross --locked + - name: Install cargo-auditable + if: ${{ !matrix.cross }} + run: cargo install cargo-auditable --locked --version 0.7.4 - name: Build (native) if: ${{ !matrix.cross }} - run: cargo build --release --target ${{ matrix.target }} + run: cargo auditable build --release --target ${{ matrix.target }} - name: Build (cross) if: ${{ matrix.cross }} - run: cross build --release --target ${{ matrix.target }} + run: cross auditable build --release --target ${{ matrix.target }} - name: Rename binary - run: cp target/${{ matrix.target }}/release/akroasis ${{ matrix.artifact }} + id: artifact + run: | + cp target/${{ matrix.target }}/release/akroasis ${{ matrix.artifact }} + echo "bin=${{ matrix.artifact }}" >> "$GITHUB_OUTPUT" + echo "BIN=${{ matrix.artifact }}" >> "$GITHUB_ENV" - name: Generate checksum - run: sha256sum ${{ matrix.artifact }} > ${{ matrix.artifact }}.sha256 + run: sha256sum "$BIN" > "$BIN.sha256" - name: Upload binary and checksum run: | gh release upload "${GITHUB_REF#refs/tags/}" \ - ${{ matrix.artifact }} \ - ${{ matrix.artifact }}.sha256 \ + "$BIN" \ + "$BIN.sha256" \ + --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Generate CycloneDX SBOM + uses: anchore/sbom-action@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0 + with: + artifact-name: ${{ steps.artifact.outputs.bin }}.cdx.json + format: cyclonedx-json + output-file: ${{ steps.artifact.outputs.bin }}.cdx.json + upload-artifact: false + upload-release-assets: false + - name: Generate SPDX SBOM + uses: anchore/sbom-action@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0 + with: + artifact-name: ${{ steps.artifact.outputs.bin }}.spdx.json + format: spdx-json + output-file: ${{ steps.artifact.outputs.bin }}.spdx.json + upload-artifact: false + upload-release-assets: false + - name: Upload SBOMs + run: | + gh release upload "${GITHUB_REF#refs/tags/}" \ + "$BIN.cdx.json" \ + "$BIN.spdx.json" \ + --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Attest binary provenance + id: attest-provenance + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 + with: + subject-path: ${{ steps.artifact.outputs.bin }} + - name: Attest CycloneDX SBOM + id: attest-cyclonedx-sbom + uses: actions/attest-sbom@5026d3663739160db546203eeaffa6aa1c51a4d6 # v1 + with: + subject-path: ${{ steps.artifact.outputs.bin }} + sbom-path: ${{ steps.artifact.outputs.bin }}.cdx.json + - name: Attest SPDX SBOM + id: attest-spdx-sbom + uses: actions/attest-sbom@5026d3663739160db546203eeaffa6aa1c51a4d6 # v1 + with: + subject-path: ${{ steps.artifact.outputs.bin }} + sbom-path: ${{ steps.artifact.outputs.bin }}.spdx.json + - name: Upload attestation bundles + run: | + cp "${{ steps.attest-provenance.outputs.bundle-path }}" "$BIN.provenance.intoto.jsonl" + cp "${{ steps.attest-cyclonedx-sbom.outputs.bundle-path }}" "$BIN.cdx.intoto.jsonl" + cp "${{ steps.attest-spdx-sbom.outputs.bundle-path }}" "$BIN.spdx.intoto.jsonl" + gh release upload "${GITHUB_REF#refs/tags/}" \ + "$BIN.provenance.intoto.jsonl" \ + "$BIN.cdx.intoto.jsonl" \ + "$BIN.spdx.intoto.jsonl" \ --clobber env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 29c6eed..0e24152 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -73,3 +73,14 @@ jobs: # .cargo/audit.toml, which mirrors deny.toml's [[advisories.ignore]] # entries — no silent --ignore flags here. run: cargo audit --deny unmaintained --deny unsound --deny yanked + + osv-scanner: + name: osv-scanner + uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@9a498708959aeaef5ef730655706c5a1df1edbc2 + with: + scan-args: |- + --lockfile=./Cargo.lock + --config=./osv-scanner.toml + permissions: + security-events: write + contents: read diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..a220798 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,9 @@ +[build.env] +passthrough = [] + +[target.aarch64-unknown-linux-gnu] +pre-build = [ + "apt-get update -q", + "apt-get install -y --no-install-recommends pkg-config libssl-dev", + "cargo install cargo-auditable --locked", +] diff --git a/crates/koinon/src/baseline.rs b/crates/koinon/src/baseline.rs index b569c09..a3cd104 100644 --- a/crates/koinon/src/baseline.rs +++ b/crates/koinon/src/baseline.rs @@ -53,7 +53,7 @@ impl Baseline { let delta = value - self.mean; self.mean += delta / (self.count as f64); // SAFETY: u64→f64 precision loss only matters beyond 2^53 observations; statistical accumulation let delta2 = value - self.mean; - self.m2 += delta * delta2; + self.m2 = delta.mul_add(delta2, self.m2); if value < self.min { self.min = value; } @@ -156,7 +156,7 @@ impl Baseline { let self_weight = self.count as f64; // SAFETY: u64→f64 precision loss only matters beyond 2^53 observations let other_weight = other.count as f64; // SAFETY: u64→f64 precision loss only matters beyond 2^53 observations let combined_weight = combined_count as f64; // SAFETY: u64→f64 precision loss only matters beyond 2^53 observations - self.mean += delta * (other_weight / combined_weight); + self.mean = delta.mul_add(other_weight / combined_weight, self.mean); self.m2 += (delta * delta).mul_add(self_weight * other_weight / combined_weight, other.m2); self.count = combined_count; if other.min < self.min { diff --git a/osv-scanner.toml b/osv-scanner.toml new file mode 100644 index 0000000..be1642b --- /dev/null +++ b/osv-scanner.toml @@ -0,0 +1,4 @@ +# OSV-Scanner advisory waiver list. +# Entries must mirror .cargo/audit.toml [[advisories.ignore]] and deny.toml +# [[advisories.ignore]] so all three scanners agree. Add entries with a +# WHY comment and the date added.