fix: attach cosign signatures and provenance bundle to release assets#438
fix: attach cosign signatures and provenance bundle to release assets#438
Conversation
OpenSSF Scorecard "Signed-Releases" check scores 0/10 because it only inspects GitHub Release assets for signature files. Add cosign keyless signing of checksums.txt (.sig + .pem) and a Sigstore provenance bundle (.sigstore.json) as release assets to achieve 8/10 scoring. Release assets: 7 existing + checksums.txt.sig + checksums.txt.pem + checksums.txt.sigstore.json = 10 total.
Pre-reviewed by 2 agents, 6 findings addressed: - docs/security.md: add cosign signing to CLI binaries bullet - docs/architecture/tech-stack.md: add cosign to CLI row and container packaging decision - CLAUDE.md: document finalize-release verification assembly behavior - cli.yml: remove unused step id, move step output to env: block
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.OpenSSF Scorecard
Scanned Files
|
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the supply chain security and verifiability of CLI releases by integrating Cosign keyless signing and Sigstore provenance bundles directly into GitHub Release assets. This change directly addresses a limitation identified by the OpenSSF Scorecard, improving the project's "Signed-Releases" score by making signature and provenance information readily available alongside release artifacts, rather than solely in GitHub's attestation store. Highlights
Changelog
Ignored Files
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds Cosign keyless signing and Sigstore provenance bundle generation to the CLI release workflow, uploads signature/certificate/provenance artifacts to releases, and updates finalize-release logic to extract and embed CLI Cosign and SLSA provenance blocks into the combined VERIFICATION section. Documentation updated accordingly. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Dev as Release Trigger
participant GH as GitHub Actions
participant Cosign as Cosign (keyless)
participant Sigstore as Sigstore/Provenance
participant Release as GitHub Release
Dev->>GH: push tag / create draft release
GH->>Cosign: install & sign cli/dist/checksums.txt
Cosign-->>GH: produces checksums.txt.sig + checksums.txt.pem
GH->>Sigstore: generate SLSA/provenance bundle for checksums
Sigstore-->>GH: returns provenance bundle (.sigstore.json)
GH->>Release: upload artifacts (checksums, .sig, .pem, .sigstore.json)
GH->>GH: embed verification blocks (CLI_COSIGN_DATA, CLI_SLSA_BUNDLE_DATA) into release body
GH->>Release: finalize release notes with VERIFICATION section
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
📝 Coding Plan
Comment |
There was a problem hiding this comment.
Code Review
This pull request updates the documentation across several files (CLAUDE.md, docs/architecture/tech-stack.md, docs/security.md) to reflect the new cosign signing and Sigstore provenance bundle generation for CLI releases. The changes accurately describe the new security features. My review focuses on improving the readability and maintainability of the updated documentation, as some of the new descriptions are quite long and dense. I've suggested breaking down long lines and list items into more structured formats like sub-bullets to make the information easier to digest.
| - **Docker**: `.github/workflows/docker.yml` — builds backend + web images, pushes to GHCR, signs with cosign. SLSA L3 provenance attestations via `actions/attest-build-provenance` (SHA-pinned, Sigstore-signed, pushed to registry). Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype (critical cutoff). CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`). | ||
| - **Matrix**: Python 3.14 | ||
| - **CLI**: `.github/workflows/cli.yml` — Go lint (`golangci-lint` + `go vet`) + test (`-race -coverprofile`) + build (cross-compile matrix: linux/darwin/windows × amd64/arm64) + vulnerability check (`govulncheck`) + fuzz testing (main-only, 30s/target, `continue-on-error`, matrix over 4 packages) on `cli/**` changes. `cli-pass` gate includes fuzz result as informational warning. GoReleaser release on `v*` tags (attaches assets to the draft Release Please release). SLSA L3 provenance attestations via `actions/attest-build-provenance` (SHA-pinned, Sigstore-signed). Post-release step appends install instructions + checksum table + provenance verification instructions to the draft release notes (while still in draft — before finalize-release publishes). | ||
| - **CLI**: `.github/workflows/cli.yml` — Go lint (`golangci-lint` + `go vet`) + test (`-race -coverprofile`) + build (cross-compile matrix: linux/darwin/windows × amd64/arm64) + vulnerability check (`govulncheck`) + fuzz testing (main-only, 30s/target, `continue-on-error`, matrix over 4 packages) on `cli/**` changes. `cli-pass` gate includes fuzz result as informational warning. GoReleaser release on `v*` tags (attaches assets to the draft Release Please release). Cosign keyless signing of `checksums.txt` (`.sig` + `.pem` attached to release). SLSA L3 provenance attestations via `actions/attest-build-provenance` (SHA-pinned, Sigstore-signed) for individual binaries and checksums file. Sigstore provenance bundle (`.sigstore.json`) attached to release for OpenSSF Scorecard signed-releases scoring. Post-release step appends install instructions + checksum table + cosign verification + provenance verification instructions to the draft release notes (while still in draft — before finalize-release publishes). |
There was a problem hiding this comment.
| - **CLA**: `.github/workflows/cla.yml` — Contributor License Agreement signature check on PRs via `contributor-assistant/github-action`. Triggers on `pull_request_target` and `issue_comment`. Skips Dependabot. Signatures stored in `.github/cla-signatures.json` on the `cla-signatures` branch (unprotected, so the action can commit directly). | ||
| - **Release**: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + **draft** GitHub Release with changelog. Tag push triggers Docker and CLI workflows to attach assets to the draft. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` (`"draft": true`) and `.github/.release-please-manifest.json`. After creating/updating a release PR, auto-updates the BSL Change Date in LICENSE to 3 years ahead. | ||
| - **Finalize Release**: `.github/workflows/finalize-release.yml` — publishes draft releases created by Release Please. Triggers on `workflow_run` completion of Docker and CLI workflows. Verifies that both workflows succeeded for the associated tag before publishing the draft. Guards against PR-triggered runs (`event != 'pull_request'`). Handles TOCTOU races (concurrent publish attempts exit cleanly). Immutable releases are enabled on the repo — once published, release assets and body cannot be modified. | ||
| - **Finalize Release**: `.github/workflows/finalize-release.yml` — publishes draft releases created by Release Please. Triggers on `workflow_run` completion of Docker and CLI workflows. Verifies that both workflows succeeded for the associated tag before publishing the draft. Extracts CLI checksums, CLI cosign verification, and container verification data from HTML comments embedded by the CLI and Docker workflows, and assembles them into a combined Verification section in the release notes. Guards against PR-triggered runs (`event != 'pull_request'`). Handles TOCTOU races (concurrent publish attempts exit cleanly). Immutable releases are enabled on the repo — once published, release assets and body cannot be modified. |
| | **Authentication** | PyJWT + argon2-cffi | JWT (HMAC HS256/384/512) for session tokens, Argon2id for password hashing, HMAC-SHA256 for API key storage (keyed with server secret). | | ||
| | **Config Format** | YAML + Pydantic validation | Human-readable config with strict validation. | | ||
| | **CLI** | Go (Cobra + charmbracelet/huh) | Cross-platform binary for Docker lifecycle management: `init`, `start`, `stop`, `status`, `logs`, `update`, `doctor`, `uninstall`, `version`. Distributed via GoReleaser + install scripts (`curl \| bash`, `irm \| iex`). SLSA Level 3 provenance attestations on all release archives. | | ||
| | **CLI** | Go (Cobra + charmbracelet/huh) | Cross-platform binary for Docker lifecycle management: `init`, `start`, `stop`, `status`, `logs`, `update`, `doctor`, `uninstall`, `version`. Distributed via GoReleaser + install scripts (`curl \| bash`, `irm \| iex`). Cosign keyless signing of checksums file (`.sig` + `.pem`). SLSA Level 3 provenance attestations on all release archives. Sigstore provenance bundle (`.sigstore.json`) attached to releases. | |
docs/security.md
Outdated
|
|
||
| - **Container images**: cosign keyless signatures (verify via `cosign verify`) + SLSA Level 3 provenance attestations (verify via `gh attestation verify`) | ||
| - **CLI binaries**: SLSA Level 3 provenance attestations (verify via `gh attestation verify`) | ||
| - **CLI binaries**: cosign keyless signature on checksums file (verify via `cosign verify-blob`) + SLSA Level 3 provenance attestations (verify via `gh attestation verify`) + Sigstore provenance bundle (`.sigstore.json`) |
There was a problem hiding this comment.
This list item contains multiple distinct pieces of information. To improve clarity and readability, consider breaking it down into sub-bullets.
| - **CLI binaries**: cosign keyless signature on checksums file (verify via `cosign verify-blob`) + SLSA Level 3 provenance attestations (verify via `gh attestation verify`) + Sigstore provenance bundle (`.sigstore.json`) | |
| - **CLI binaries**: | |
| - cosign keyless signature on checksums file (verify via `cosign verify-blob`) | |
| - SLSA Level 3 provenance attestations (verify via `gh attestation verify`) | |
| - Sigstore provenance bundle (`.sigstore.json`) |
Greptile SummaryThis PR wires up cosign keyless signing and a Sigstore provenance bundle for CLI releases, targeting the OpenSSF Scorecard "Signed-Releases" check by attaching
Confidence Score: 4/5
Important Files Changed
Prompt To Fix All With AIThis is a comment left during a code review.
Path: .github/workflows/finalize-release.yml
Line: 159
Comment:
**Misleading section heading for cosign data**
`"### CLI Binary Signatures"` implies the binary archives (`.tar.gz`/`.zip`) are individually signed with cosign, but only `checksums.txt` is signed. A user who reads this heading and then runs `cosign verify-blob` on a binary archive will get a failure. A more accurate heading would be `"### CLI Checksums Signature"` or `"### Checksums File Signature"`, making it clear that the signed artifact is `checksums.txt` (which transitively covers the binaries through their SHA-256 entries).
```suggestion
VERIFICATION="$(printf '%s\n\n### CLI Checksums Signature\n\n%s\n' "$VERIFICATION" "$CLI_COSIGN")"
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: .github/workflows/cli.yml
Line: 296-300
Comment:
**Consider failing explicitly if signing produces empty output files**
`cosign sign-blob --yes` exits zero on success, but if the OIDC token is unavailable or Fulcio is unreachable the command may still exit zero while producing empty `.sig`/`.pem` files. An empty certificate uploaded to the release would silently break verification for every user.
Adding a guard after signing (similar to the existing `Validate checksums file` check) would catch this before the upload step:
```bash
cosign sign-blob --yes cli/dist/checksums.txt \
--output-signature cli/dist/checksums.txt.sig \
--output-certificate cli/dist/checksums.txt.pem
if [ ! -s cli/dist/checksums.txt.sig ] || [ ! -s cli/dist/checksums.txt.pem ]; then
echo "::error::cosign produced empty signature or certificate — signing may have failed silently"
exit 1
fi
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 5642a74 |
- Add .sigstore.json verification instructions (CLI_SLSA_BUNDLE_DATA block) to release notes and finalize-release extraction - Add inline comments distinguishing dual attestation steps (subject-checksums vs subject-path) - Use sub-bullets in docs/security.md for CLI binaries readability
attest-build-provenance@v4 produces SLSA v1.0 predicates, but cosign defaults to v0.2 (slsaprovenance). Without --type slsaprovenance1 the verification command would fail with a predicate type mismatch.
| fi | ||
|
|
||
| if [ -n "$CLI_COSIGN" ]; then | ||
| VERIFICATION="$(printf '%s\n\n### CLI Binary Signatures\n\n%s\n' "$VERIFICATION" "$CLI_COSIGN")" |
There was a problem hiding this comment.
Misleading section heading for cosign data
"### CLI Binary Signatures" implies the binary archives (.tar.gz/.zip) are individually signed with cosign, but only checksums.txt is signed. A user who reads this heading and then runs cosign verify-blob on a binary archive will get a failure. A more accurate heading would be "### CLI Checksums Signature" or "### Checksums File Signature", making it clear that the signed artifact is checksums.txt (which transitively covers the binaries through their SHA-256 entries).
| VERIFICATION="$(printf '%s\n\n### CLI Binary Signatures\n\n%s\n' "$VERIFICATION" "$CLI_COSIGN")" | |
| VERIFICATION="$(printf '%s\n\n### CLI Checksums Signature\n\n%s\n' "$VERIFICATION" "$CLI_COSIGN")" |
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/finalize-release.yml
Line: 159
Comment:
**Misleading section heading for cosign data**
`"### CLI Binary Signatures"` implies the binary archives (`.tar.gz`/`.zip`) are individually signed with cosign, but only `checksums.txt` is signed. A user who reads this heading and then runs `cosign verify-blob` on a binary archive will get a failure. A more accurate heading would be `"### CLI Checksums Signature"` or `"### Checksums File Signature"`, making it clear that the signed artifact is `checksums.txt` (which transitively covers the binaries through their SHA-256 entries).
```suggestion
VERIFICATION="$(printf '%s\n\n### CLI Checksums Signature\n\n%s\n' "$VERIFICATION" "$CLI_COSIGN")"
```
How can I resolve this? If you propose a fix, please make it concise.| - name: Sign checksums with cosign (keyless) | ||
| run: | | ||
| cosign sign-blob --yes cli/dist/checksums.txt \ | ||
| --output-signature cli/dist/checksums.txt.sig \ | ||
| --output-certificate cli/dist/checksums.txt.pem |
There was a problem hiding this comment.
Consider failing explicitly if signing produces empty output files
cosign sign-blob --yes exits zero on success, but if the OIDC token is unavailable or Fulcio is unreachable the command may still exit zero while producing empty .sig/.pem files. An empty certificate uploaded to the release would silently break verification for every user.
Adding a guard after signing (similar to the existing Validate checksums file check) would catch this before the upload step:
cosign sign-blob --yes cli/dist/checksums.txt \
--output-signature cli/dist/checksums.txt.sig \
--output-certificate cli/dist/checksums.txt.pem
if [ ! -s cli/dist/checksums.txt.sig ] || [ ! -s cli/dist/checksums.txt.pem ]; then
echo "::error::cosign produced empty signature or certificate — signing may have failed silently"
exit 1
fiPrompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/cli.yml
Line: 296-300
Comment:
**Consider failing explicitly if signing produces empty output files**
`cosign sign-blob --yes` exits zero on success, but if the OIDC token is unavailable or Fulcio is unreachable the command may still exit zero while producing empty `.sig`/`.pem` files. An empty certificate uploaded to the release would silently break verification for every user.
Adding a guard after signing (similar to the existing `Validate checksums file` check) would catch this before the upload step:
```bash
cosign sign-blob --yes cli/dist/checksums.txt \
--output-signature cli/dist/checksums.txt.sig \
--output-certificate cli/dist/checksums.txt.pem
if [ ! -s cli/dist/checksums.txt.sig ] || [ ! -s cli/dist/checksums.txt.pem ]; then
echo "::error::cosign produced empty signature or certificate — signing may have failed silently"
exit 1
fi
```
How can I resolve this? If you propose a fix, please make it concise.🤖 I have created a release *beep* *boop* --- ## [0.2.4](v0.2.3...v0.2.4) (2026-03-15) ### Bug Fixes * attach cosign signatures and provenance bundle to release assets ([#438](#438)) ([f191a4d](f191a4d)) * create git tag explicitly for draft releases ([#432](#432)) ([1f5120e](1f5120e)) * docker healthcheck, CI optimization, and container hardening ([#436](#436)) ([4d32bca](4d32bca)) * ensure security headers on all HTTP responses ([#437](#437)) ([837f2fc](837f2fc)) * make install scripts usable immediately without terminal restart ([#433](#433)) ([b45533c](b45533c)) * migrate pids_limit to deploy.resources.limits.pids ([#439](#439)) ([66b94fd](66b94fd)) ### Refactoring * redesign release notes layout ([#434](#434)) ([239aaf7](239aaf7)) ### Maintenance * **site:** replace hero CTA with license link and scroll arrow ([#440](#440)) ([56af41c](56af41c)) * **web:** adopt @vue/tsconfig preset ([#435](#435)) ([7d4b214](7d4b214)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
🤖 I have created a release *beep* *boop* --- ## [0.2.4](v0.2.3...v0.2.4) (2026-03-15) ### Bug Fixes * attach cosign signatures and provenance bundle to release assets ([#438](#438)) ([f191a4d](f191a4d)) * create git tag explicitly for draft releases ([#432](#432)) ([1f5120e](1f5120e)) * docker healthcheck, CI optimization, and container hardening ([#436](#436)) ([4d32bca](4d32bca)) * ensure security headers on all HTTP responses ([#437](#437)) ([837f2fc](837f2fc)) * make install scripts usable immediately without terminal restart ([#433](#433)) ([b45533c](b45533c)) * migrate pids_limit to deploy.resources.limits.pids ([#439](#439)) ([66b94fd](66b94fd)) * use cosign --bundle flag for checksums signing ([#443](#443)) ([19735b9](19735b9)) ### Refactoring * redesign release notes layout ([#434](#434)) ([239aaf7](239aaf7)) ### Maintenance * **main:** release 0.2.4 ([#431](#431)) ([63b03c4](63b03c4)) * remove stale v0.2.4 changelog section from failed release ([#446](#446)) ([769de10](769de10)) * reset version to 0.2.3 for re-release ([#444](#444)) ([8579993](8579993)) * **site:** replace hero CTA with license link and scroll arrow ([#440](#440)) ([56af41c](56af41c)) * **web:** adopt @vue/tsconfig preset ([#435](#435)) ([7d4b214](7d4b214)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Summary
checksums.txtin the CLI release job — produces.sig(detached signature) and.pem(Fulcio certificate) attached to the GitHub Release.sigstore.json) forchecksums.txtvia a separateattest-build-provenanceattestation, uploaded as a release assetCLI_COSIGN_DATAfrom hidden HTML comments and render a "CLI Binary Signatures" subsection in the combined Verification sectiondocs/security.md,docs/architecture/tech-stack.md,CLAUDE.md) to reflect the new signing and provenance capabilitiesWhy: OpenSSF Scorecard "Signed-Releases" check scores 0/10 because it only inspects GitHub Release assets for signature/provenance files. We have SLSA L3 attestations in GitHub's attestation store, but no
.sigor.sigstore.jsonfiles on the release itself. This PR adds them, targeting 8/10 scoring.Release assets after this change: 7 existing +
checksums.txt.sig+checksums.txt.pem+checksums.txt.sigstore.json= 10 total.Test plan
docker.yml(ba7bc0a3...)$BUNDLEis passed viaenv:block (not direct interpolation)v*tag release exercises the full pipeline — check draft release has all 10 assets before finalize-release publishesReview coverage
Pre-reviewed by 2 agents (docs-consistency, infra-reviewer), 6 findings addressed:
id: atteststep referenceenv:block for security consistencydocs/security.md,docs/architecture/tech-stack.md,CLAUDE.mdfor documentation accuracy🤖 Generated with Claude Code