ci: harden release provenance - #27
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Enterprise Run ID: 📒 Files selected for processing (1)
📜 Recent review details
|
| Layer / File(s) | Summary |
|---|---|
Harden release workflow .github/workflows/release.yml |
The workflow pins actions, grants attestation and OIDC permissions, attests release assets, and publishes them through gh with retries and clobbering. |
Document release verification CHANGELOG.md, DISTRIBUTION_CHANNELS.md |
The documentation records immutable action pins, build-provenance attestations, SHA-256 verification, and the attestation verification command. |
Estimated code review effort: 3 (Moderate) | ~20 minutes
Sequence Diagram(s)
sequenceDiagram
participant ReleaseWorkflow
participant AttestationAction
participant GitHubCLI
participant GitHubRelease
ReleaseWorkflow->>AttestationAction: Attest archive, checksum, and manifest
ReleaseWorkflow->>GitHubCLI: Verify or create tagged release
GitHubCLI->>GitHubRelease: Upload release assets with clobbering
Suggested reviewers: juanpflores
Poem
A rabbit pins each action tight,
Signs release files with proof of light.
Checksums guide the hopping trail,
Retries help the upload sail.
“Verified!” sings the bunny bright.
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The title clearly and concisely describes the PR's main change: strengthening release provenance and publication integrity. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Agent Guidance Structure | ✅ Passed | The PR changes only the release workflow and two documentation files; it does not change skills, agent guidance, native command definitions, or manifests. |
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
- Create stacked PR
- Commit on current branch
✨ Simplify code
- Create PR with simplified code
- Commit simplified code in branch
esthor/harden-release-provenance
Comment @coderabbitai help to get the list of available commands.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/release.yml (1)
71-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider replacing the third-party release action with
gh release.
zizmorreports this action as superfluous. A script usinggh releasecan create a release with asset paths, generated notes, and tag verification. Preserve existing-release, overwrite, and retry behavior before switching. Verify the runner's GitHub CLI version and command semantics. (cli.github.com)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 71 - 72, Replace the softprops/action-gh-release step with a script using the GitHub CLI’s gh release commands, preserving the current existing-release handling, overwrite behavior, retries, asset paths, generated notes, and tag verification. First confirm the runner’s gh version and release-create command semantics, then update the workflow step to provide equivalent behavior without the third-party action.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In @.github/workflows/release.yml:
- Around line 71-72: Replace the softprops/action-gh-release step with a script
using the GitHub CLI’s gh release commands, preserving the current
existing-release handling, overwrite behavior, retries, asset paths, generated
notes, and tag verification. First confirm the runner’s gh version and
release-create command semantics, then update the workflow step to provide
equivalent behavior without the third-party action.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: dfc6d04c-0d94-4f40-bbef-ddd7ea4a099c
📒 Files selected for processing (3)
.github/workflows/release.ymlCHANGELOG.mdDISTRIBUTION_CHANNELS.md
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
coderabbitai/bitbucket(manual)
📜 Review details
⚠️ CI failures not shown inline (2)
GitHub Actions: Required approver / verify: ci: harden release provenance
Conclusion: failure
##[group]Run set -euo pipefail
�[36;1mset -euo pipefail�[0m
�[36;1m�[0m
�[36;1mowner="${REPOSITORY%%/*}"�[0m
�[36;1mrepo="${REPOSITORY#*/}"�[0m
�[36;1m�[0m
�[36;1mresponse="$(�[0m
�[36;1m gh api graphql \�[0m
�[36;1m -f owner="$owner" \�[0m
�[36;1m -f repo="$repo" \�[0m
�[36;1m -F number="$PR_NUMBER" \�[0m
�[36;1m -f query='�[0m
�[36;1m query($owner: String!, $repo: String!, $number: Int!) {�[0m
�[36;1m repository(owner: $owner, name: $repo) {�[0m
�[36;1m pullRequest(number: $number) {�[0m
�[36;1m isDraft�[0m
�[36;1m reviews(last: 100) {�[0m
�[36;1m nodes {�[0m
�[36;1m author {�[0m
�[36;1m login�[0m
�[36;1m }�[0m
�[36;1m commit {�[0m
�[36;1m oid�[0m
�[36;1m }�[0m
�[36;1m state�[0m
�[36;1m submittedAt�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m '�[0m
�[36;1m)"�[0m
�[36;1m�[0m
�[36;1mis_draft="$(jq -r '.data.repository.pullRequest.isDraft' <<<"$response")"�[0m
�[36;1mif [[ "$is_draft" == "true" ]]; then�[0m
�[36;1m echo "Draft PRs cannot merge; approver gate will run when ready for review."�[0m
�[36;1m exit 0�[0m
�[36;1mfi�[0m
�[36;1m�[0m
�[36;1mapproved_by="$(�[0m
�[36;1m jq -r \�[0m
�[36;1m --arg head "$HEAD_SHA" \�[0m
�[36;1m --argjson approvers "$(jq -Rc 'split(",")' <<<"$REQUIRED_APPROVERS")" \�[0m
�[36;1m '�[0m
�[36;1m .data.repository.pullRequest.reviews.nodes�[0m
�[36;1m | map(select((.author.login // "") as $login | $approvers | index($login)))�[0m
�[36;1m | sort_by(.author.login)�[0m
�[36;1m | group_by(.author.login)�[0m
�[36;1m | map(max_by(.submittedAt))�[0m
�[36;1m | map(select(.state == "APPROVED" and .commit.oid == $head))�[0m
�[36;1m | first.author.login // empty�[0m
�[36;1m ...
GitHub Actions: Required approver / 0_verify.txt: ci: harden release provenance
Conclusion: failure
##[group]Run set -euo pipefail
�[36;1mset -euo pipefail�[0m
�[36;1m�[0m
�[36;1mowner="${REPOSITORY%%/*}"�[0m
�[36;1mrepo="${REPOSITORY#*/}"�[0m
�[36;1m�[0m
�[36;1mresponse="$(�[0m
�[36;1m gh api graphql \�[0m
�[36;1m -f owner="$owner" \�[0m
�[36;1m -f repo="$repo" \�[0m
�[36;1m -F number="$PR_NUMBER" \�[0m
�[36;1m -f query='�[0m
�[36;1m query($owner: String!, $repo: String!, $number: Int!) {�[0m
�[36;1m repository(owner: $owner, name: $repo) {�[0m
�[36;1m pullRequest(number: $number) {�[0m
�[36;1m isDraft�[0m
�[36;1m reviews(last: 100) {�[0m
�[36;1m nodes {�[0m
�[36;1m author {�[0m
�[36;1m login�[0m
�[36;1m }�[0m
�[36;1m commit {�[0m
�[36;1m oid�[0m
�[36;1m }�[0m
�[36;1m state�[0m
�[36;1m submittedAt�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m '�[0m
�[36;1m)"�[0m
�[36;1m�[0m
�[36;1mis_draft="$(jq -r '.data.repository.pullRequest.isDraft' <<<"$response")"�[0m
�[36;1mif [[ "$is_draft" == "true" ]]; then�[0m
�[36;1m echo "Draft PRs cannot merge; approver gate will run when ready for review."�[0m
�[36;1m exit 0�[0m
�[36;1mfi�[0m
�[36;1m�[0m
�[36;1mapproved_by="$(�[0m
�[36;1m jq -r \�[0m
�[36;1m --arg head "$HEAD_SHA" \�[0m
�[36;1m --argjson approvers "$(jq -Rc 'split(",")' <<<"$REQUIRED_APPROVERS")" \�[0m
�[36;1m '�[0m
�[36;1m .data.repository.pullRequest.reviews.nodes�[0m
�[36;1m | map(select((.author.login // "") as $login | $approvers | index($login)))�[0m
�[36;1m | sort_by(.author.login)�[0m
�[36;1m | group_by(.author.login)�[0m
�[36;1m | map(max_by(.submittedAt))�[0m
�[36;1m | map(select(.state == "APPROVED" and .commit.oid == $head))�[0m
�[36;1m | first.author.login // empty�[0m
�[36;1m ...
🧰 Additional context used
🪛 LanguageTool
DISTRIBUTION_CHANNELS.md
[uncategorized] ~12-~12: The official name of this software platform is spelled with a capital “H”.
Context: ...ers | In development, not user-facing | .github/workflows/release.yml | Workflow publi...
(GITHUB)
🪛 zizmor (1.29.0)
.github/workflows/release.yml
[info] 72-72: action functionality is already included by the runner (superfluous-actions): use gh release in a script step
(superfluous-actions)
🔇 Additional comments (5)
.github/workflows/release.yml (4)
14-23: LGTM!
63-69: LGTM!
71-72: 🗄️ Data Integrity & IntegrationSerialize publication for one tag.
If Lines 1-8 do not already define a concurrency group, two runs for one tag can upload to the same release at the same time. The attestation covers the complete file set, but the release action uploads files separately and overwrites existing assets by default. The final release can therefore mix an archive, checksum, and manifest from different runs. Add a concurrency group keyed by
github.ref, or verify that immutable releases prevent concurrent overwrites. (github.com)
9-12: 🔒 Security & PrivacyEstablish protection for
v*tags before relying on this release flow.The workflow runs for every pushed
v*tag and has release and attestation write permissions. Repository files do not establish a ruleset that restricts tag creation, updates, and deletion. Confirm the active repository rule, then retain “protectedv*tags” inDISTRIBUTION_CHANNELS.md.CHANGELOG.md (1)
21-22: LGTM!
|
Addressed the hosted review findings in |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Around line 76-112: Before relying on the release workflow’s write-capable
GH_TOKEN, configure repository tag protection for the v* pattern through an
appropriate tag ruleset. Restrict creation, deletion, and update operations to
authorized release maintainers, and ensure the protection is established before
this workflow is exposed as a user-facing release channel.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: 7d0ba916-4d96-4b6d-86c3-557028787400
📒 Files selected for processing (2)
.github/workflows/release.ymlDISTRIBUTION_CHANNELS.md
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
coderabbitai/bitbucket(manual)
📜 Review details
⚠️ CI failures not shown inline (2)
GitHub Actions: Required approver / 0_verify.txt: ci: harden release provenance
Conclusion: failure
##[group]Run set -euo pipefail
�[36;1mset -euo pipefail�[0m
�[36;1m�[0m
�[36;1mowner="${REPOSITORY%%/*}"�[0m
�[36;1mrepo="${REPOSITORY#*/}"�[0m
�[36;1m�[0m
�[36;1mresponse="$(�[0m
�[36;1m gh api graphql \�[0m
�[36;1m -f owner="$owner" \�[0m
�[36;1m -f repo="$repo" \�[0m
�[36;1m -F number="$PR_NUMBER" \�[0m
�[36;1m -f query='�[0m
�[36;1m query($owner: String!, $repo: String!, $number: Int!) {�[0m
�[36;1m repository(owner: $owner, name: $repo) {�[0m
�[36;1m pullRequest(number: $number) {�[0m
�[36;1m isDraft�[0m
�[36;1m reviews(last: 100) {�[0m
�[36;1m nodes {�[0m
�[36;1m author {�[0m
�[36;1m login�[0m
�[36;1m }�[0m
�[36;1m commit {�[0m
�[36;1m oid�[0m
�[36;1m }�[0m
�[36;1m state�[0m
�[36;1m submittedAt�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m '�[0m
�[36;1m)"�[0m
�[36;1m�[0m
�[36;1mis_draft="$(jq -r '.data.repository.pullRequest.isDraft' <<<"$response")"�[0m
�[36;1mif [[ "$is_draft" == "true" ]]; then�[0m
�[36;1m echo "Draft PRs cannot merge; approver gate will run when ready for review."�[0m
�[36;1m exit 0�[0m
�[36;1mfi�[0m
�[36;1m�[0m
�[36;1mapproved_by="$(�[0m
�[36;1m jq -r \�[0m
�[36;1m --arg head "$HEAD_SHA" \�[0m
�[36;1m --argjson approvers "$(jq -Rc 'split(",")' <<<"$REQUIRED_APPROVERS")" \�[0m
�[36;1m '�[0m
�[36;1m .data.repository.pullRequest.reviews.nodes�[0m
�[36;1m | map(select((.author.login // "") as $login | $approvers | index($login)))�[0m
�[36;1m | sort_by(.author.login)�[0m
�[36;1m | group_by(.author.login)�[0m
�[36;1m | map(max_by(.submittedAt))�[0m
�[36;1m | map(select(.state == "APPROVED" and .commit.oid == $head))�[0m
�[36;1m | first.author.login // empty�[0m
�[36;1m ...
GitHub Actions: Required approver / verify: ci: harden release provenance
Conclusion: failure
##[group]Run set -euo pipefail
�[36;1mset -euo pipefail�[0m
�[36;1m�[0m
�[36;1mowner="${REPOSITORY%%/*}"�[0m
�[36;1mrepo="${REPOSITORY#*/}"�[0m
�[36;1m�[0m
�[36;1mresponse="$(�[0m
�[36;1m gh api graphql \�[0m
�[36;1m -f owner="$owner" \�[0m
�[36;1m -f repo="$repo" \�[0m
�[36;1m -F number="$PR_NUMBER" \�[0m
�[36;1m -f query='�[0m
�[36;1m query($owner: String!, $repo: String!, $number: Int!) {�[0m
�[36;1m repository(owner: $owner, name: $repo) {�[0m
�[36;1m pullRequest(number: $number) {�[0m
�[36;1m isDraft�[0m
�[36;1m reviews(last: 100) {�[0m
�[36;1m nodes {�[0m
�[36;1m author {�[0m
�[36;1m login�[0m
�[36;1m }�[0m
�[36;1m commit {�[0m
�[36;1m oid�[0m
�[36;1m }�[0m
�[36;1m state�[0m
�[36;1m submittedAt�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m }�[0m
�[36;1m '�[0m
�[36;1m)"�[0m
�[36;1m�[0m
�[36;1mis_draft="$(jq -r '.data.repository.pullRequest.isDraft' <<<"$response")"�[0m
�[36;1mif [[ "$is_draft" == "true" ]]; then�[0m
�[36;1m echo "Draft PRs cannot merge; approver gate will run when ready for review."�[0m
�[36;1m exit 0�[0m
�[36;1mfi�[0m
�[36;1m�[0m
�[36;1mapproved_by="$(�[0m
�[36;1m jq -r \�[0m
�[36;1m --arg head "$HEAD_SHA" \�[0m
�[36;1m --argjson approvers "$(jq -Rc 'split(",")' <<<"$REQUIRED_APPROVERS")" \�[0m
�[36;1m '�[0m
�[36;1m .data.repository.pullRequest.reviews.nodes�[0m
�[36;1m | map(select((.author.login // "") as $login | $approvers | index($login)))�[0m
�[36;1m | sort_by(.author.login)�[0m
�[36;1m | group_by(.author.login)�[0m
�[36;1m | map(max_by(.submittedAt))�[0m
�[36;1m | map(select(.state == "APPROVED" and .commit.oid == $head))�[0m
�[36;1m | first.author.login // empty�[0m
�[36;1m ...
🧰 Additional context used
🪛 LanguageTool
DISTRIBUTION_CHANNELS.md
[uncategorized] ~12-~12: The official name of this software platform is spelled with a capital “H”.
Context: ...ers | In development, not user-facing | .github/workflows/release.yml | Workflow publi...
(GITHUB)
🔇 Additional comments (4)
DISTRIBUTION_CHANNELS.md (1)
12-12: LGTM!Also applies to: 25-26
.github/workflows/release.yml (3)
14-17: LGTM!
24-27: LGTM!
67-73: LGTM!
Summary
idempotent release creation, and clobber-on-rerun asset uploads
Why
This repository distributes installable agent guidance from version tags. Immutable Action references reduce dependency-substitution risk, while GitHub attestations let consumers verify that release assets were produced by this repository's tagged workflow.
Validation
git diff --check.github/workflows/release.ymlsuccessfully with Ruby YAMLactions/attestpermission andsubject-pathcontract against its upstream documentationrunblock successfully withbash -ngh release create --verify-tag --generate-notesandgh release upload --clobberagainst the current GitHub CLI documentationserializing publication by
github.ref, and using idempotent CLI publicationRepository setting
Complete: active ruleset
Protect version tagstargetsrefs/tags/v*and restricts creation, update, deletion, and non-fast-forward changes to repository administrators.Summary by CodeRabbit
Release Improvements
Documentation