Skip to content

ci: harden release provenance - #27

Open
esthor wants to merge 1 commit into
mainfrom
esthor/harden-release-provenance
Open

ci: harden release provenance#27
esthor wants to merge 1 commit into
mainfrom
esthor/harden-release-provenance

Conversation

@esthor

@esthor esthor commented Aug 9, 2026

Copy link
Copy Markdown

Summary

  • pin every third-party release Action to an immutable commit SHA
  • generate GitHub build-provenance attestations for the release archive, checksum, and manifest
  • document how consumers can verify published assets

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
  • parsed .github/workflows/release.yml successfully with Ruby YAML
  • verified each pinned tag-to-commit mapping against its upstream GitHub repository
  • verified the actions/attest permission and subject-path contract against its upstream documentation
  • coderabbit review --agent --uncommitted — tightened the documented
    verification command to bind it to the signer workflow and source tag; kept
    artifact-metadata: write because the pinned action documents it as required
    to 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

    • Release assets now include signed build-provenance attestations.
    • Release automation uses pinned, verified actions and restricted credentials.
  • Documentation

    • Added guidance for verifying release archives with SHA-256 checksums.
    • Documented how to validate build-provenance attestations for published assets.
    • Updated the changelog with the latest release security improvements.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The 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 gh attestation verify command.

Suggested reviewers: juanpflores, nehal-a2z

Poem

A rabbit checks the release trail,
While signed attestations sail.
Pinned actions hop in line,
Hashes sparkle, proofs align.
“Verified!” the bunny sings,
Safe release metadata brings.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: hardening release provenance in CI.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch esthor/harden-release-provenance
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.github/workflows/release.yml (1)

71-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider replacing the third-party release action with gh release.

zizmor reports this action as superfluous. A script using gh release can 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

📥 Commits

Reviewing files that changed from the base of the PR and between bbb4ab2 and 3e0e937.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • CHANGELOG.md
  • DISTRIBUTION_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

View job details

##[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

View job details

##[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 & Integration

Serialize 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 & Privacy

Establish 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 “protected v* tags” in DISTRIBUTION_CHANNELS.md.

CHANGELOG.md (1)

21-22: LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant