ci: harden release provenance - #27
Conversation
📝 WalkthroughWalkthroughThe release workflow now grants attestation, artifact-metadata, and OIDC permissions. It pins checkout and GitHub release actions to specific commits and disables persisted checkout credentials. It attests the archive, checksum, and manifest. The changelog records these changes. Distribution documentation describes SHA-256 verification and build-provenance attestation, and adds an Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
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!
Summary
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 documentationcoderabbit review --agent --uncommitted— tightened the documentedverification command to bind it to the signer workflow and source tag; kept
artifact-metadata: writebecause the pinned action documents it as requiredto create the artifact storage record
Follow-up repository setting
Protect
v*tags so only authorized maintainers can create, update, or delete release tags.Summary by CodeRabbit
Security
Documentation