From 40b729a52614dd6fe6204e9afa65dd89bb5707de Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 21 Sep 2026 23:48:10 +1200 Subject: [PATCH] Use symbolic action references in release workflows --- .github/workflows/release-prepare.yaml | 4 ++-- .github/workflows/release-publish.yaml | 17 ++++++++++------- .github/workflows/release-validate.yaml | 4 ++-- context/getting-started.md | 4 +++- guides/getting-started/readme.md | 4 +++- templates/release-prepare.yaml.erb | 4 ++-- templates/release-publish.yaml.erb | 17 ++++++++++------- templates/release-validate.yaml.erb | 4 ++-- test/bake/gem/github/setup.rb | 19 +++++++++++++++++++ 9 files changed, 53 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release-prepare.yaml b/.github/workflows/release-prepare.yaml index bfaa291..19b69db 100644 --- a/.github/workflows/release-prepare.yaml +++ b/.github/workflows/release-prepare.yaml @@ -25,12 +25,12 @@ jobs: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@v7 with: ref: main fetch-depth: 0 persist-credentials: false - - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" bundler-cache: true diff --git a/.github/workflows/release-publish.yaml b/.github/workflows/release-publish.yaml index 0ce48d8..0879424 100644 --- a/.github/workflows/release-publish.yaml +++ b/.github/workflows/release-publish.yaml @@ -27,11 +27,11 @@ jobs: release: ${{ steps.inspect.outputs.release }} commit: ${{ steps.inspect.outputs.commit }} steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false - - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" bundler-cache: true @@ -52,12 +52,15 @@ jobs: id-token: write attestations: write steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@v7 with: + # Inspection verified this merged commit belongs to the default branch. + # Checkout v7 also blocks merged fork PRs without this explicit opt-in. + allow-unsafe-pr-checkout: true ref: ${{ needs.inspect.outputs.commit }} fetch-depth: 0 persist-credentials: false - - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" rubygems: '4.0.21' @@ -76,7 +79,7 @@ jobs: - name: Attest gem and release receipt if: steps.build.outputs.restored != 'true' id: attest - uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 + uses: actions/attest@v4 with: subject-path: | ${{ steps.build.outputs.package }} @@ -88,13 +91,13 @@ jobs: run: cp "$ATTESTATION_BUNDLE" pkg/provenance.sigstore.json - name: Preserve release before upload if: steps.build.outputs.restored != 'true' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@v7 with: name: ${{ steps.build.outputs.artifact }} path: pkg/ if-no-files-found: error retention-days: 90 - - uses: rubygems/configure-rubygems-credentials@2a7221c7c44c30ebc68eda38fa25c60b918245c7 + - uses: rubygems/configure-rubygems-credentials@main - name: Verify, publish, and finalize env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release-validate.yaml b/.github/workflows/release-validate.yaml index 3700ebc..0f91f8a 100644 --- a/.github/workflows/release-validate.yaml +++ b/.github/workflows/release-validate.yaml @@ -15,12 +15,12 @@ jobs: name: Release validation runs-on: ubuntu-latest steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@v7 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 persist-credentials: false - - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" bundler-cache: true diff --git a/context/getting-started.md b/context/getting-started.md index 7d874f3..19b213f 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -18,7 +18,7 @@ bundle exec bake agent:context:install bundle exec bake gem:github:setup:plan ``` -Setup adds three pinned workflows, `config/release.yaml`, native ruleset payloads, and `.github/releasing.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. Setup does not replace other publishers: remove conflicting release workflows during migration. +Setup adds three release workflows, `config/release.yaml`, native ruleset payloads, and `.github/releasing.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. Setup does not replace other publishers: remove conflicting release workflows during migration. To adopt template fixes after upgrading the gem, start from a clean working tree, edit `config/release.yaml` as needed, and regenerate: @@ -29,6 +29,8 @@ git diff The task updates the managed workflows, policy payloads, configuration formatting, and maintainer instructions directly in the working tree and returns the changed paths. It does not stage, commit, or change remote settings. An agent or maintainer can review the diff and selectively keep changes, restoring repository-specific customizations from Git where needed. Commit or stash existing edits first: generated files are replaced by the current templates. Repeating an update produces no further changes; intentionally retained customizations will appear in later update diffs. Apply remote rulesets after the corresponding workflows are running. +Release workflows follow `bake modernize` action versions and use moving major tags where upstream provides them. These tags receive upstream updates automatically; full commit hashes select fixed revisions. The RubyGems credentials action uses its [documented `@main` reference](https://github.com/rubygems/configure-rubygems-credentials#trusted-publisher-recommended), since upstream does not provide a moving major tag. Repositories that require fixed revisions can customize these references. + The default is two approvals with explicit administrator bypass, dismissed stale reviews, approval of the last push, strict up-to-date CI, and immutable default-branch history/release tags. These branch rules affect **all PRs** into the default branch. Ordinary administrator reviews count as one review. A human who dispatches a bot-authored PR is not its author under GitHub's native rules. Review `gem:github:setup:plan`, merge the setup PR, and confirm that **Release validation** and every selected check run. Then apply the four managed rulesets using an administrator's `gh` login: diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index 7d874f3..19b213f 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -18,7 +18,7 @@ bundle exec bake agent:context:install bundle exec bake gem:github:setup:plan ``` -Setup adds three pinned workflows, `config/release.yaml`, native ruleset payloads, and `.github/releasing.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. Setup does not replace other publishers: remove conflicting release workflows during migration. +Setup adds three release workflows, `config/release.yaml`, native ruleset payloads, and `.github/releasing.md`. Identical reruns do nothing; conflicting existing files stop before any file is written. Setup does not replace other publishers: remove conflicting release workflows during migration. To adopt template fixes after upgrading the gem, start from a clean working tree, edit `config/release.yaml` as needed, and regenerate: @@ -29,6 +29,8 @@ git diff The task updates the managed workflows, policy payloads, configuration formatting, and maintainer instructions directly in the working tree and returns the changed paths. It does not stage, commit, or change remote settings. An agent or maintainer can review the diff and selectively keep changes, restoring repository-specific customizations from Git where needed. Commit or stash existing edits first: generated files are replaced by the current templates. Repeating an update produces no further changes; intentionally retained customizations will appear in later update diffs. Apply remote rulesets after the corresponding workflows are running. +Release workflows follow `bake modernize` action versions and use moving major tags where upstream provides them. These tags receive upstream updates automatically; full commit hashes select fixed revisions. The RubyGems credentials action uses its [documented `@main` reference](https://github.com/rubygems/configure-rubygems-credentials#trusted-publisher-recommended), since upstream does not provide a moving major tag. Repositories that require fixed revisions can customize these references. + The default is two approvals with explicit administrator bypass, dismissed stale reviews, approval of the last push, strict up-to-date CI, and immutable default-branch history/release tags. These branch rules affect **all PRs** into the default branch. Ordinary administrator reviews count as one review. A human who dispatches a bot-authored PR is not its author under GitHub's native rules. Review `gem:github:setup:plan`, merge the setup PR, and confirm that **Release validation** and every selected check run. Then apply the four managed rulesets using an administrator's `gh` login: diff --git a/templates/release-prepare.yaml.erb b/templates/release-prepare.yaml.erb index a9ff48c..8872f31 100644 --- a/templates/release-prepare.yaml.erb +++ b/templates/release-prepare.yaml.erb @@ -25,12 +25,12 @@ jobs: if: github.ref == 'refs/heads/<%= branch %>' runs-on: ubuntu-latest steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@v7 with: ref: <%= branch %> fetch-depth: 0 persist-credentials: false - - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: <%= ruby.to_json %> bundler-cache: true diff --git a/templates/release-publish.yaml.erb b/templates/release-publish.yaml.erb index 5dad41b..f694601 100644 --- a/templates/release-publish.yaml.erb +++ b/templates/release-publish.yaml.erb @@ -27,11 +27,11 @@ jobs: release: ${{ steps.inspect.outputs.release }} commit: ${{ steps.inspect.outputs.commit }} steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false - - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: <%= ruby.to_json %> bundler-cache: true @@ -52,12 +52,15 @@ jobs: id-token: write attestations: write steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@v7 with: + # Inspection verified this merged commit belongs to the default branch. + # Checkout v7 also blocks merged fork PRs without this explicit opt-in. + allow-unsafe-pr-checkout: true ref: ${{ needs.inspect.outputs.commit }} fetch-depth: 0 persist-credentials: false - - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: <%= ruby.to_json %> rubygems: '4.0.21' @@ -78,7 +81,7 @@ jobs: - name: Attest gem and release receipt if: steps.build.outputs.restored != 'true' id: attest - uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 + uses: actions/attest@v4 with: subject-path: | ${{ steps.build.outputs.package }} @@ -90,13 +93,13 @@ jobs: run: cp "$ATTESTATION_BUNDLE" pkg/provenance.sigstore.json - name: Preserve release before upload if: steps.build.outputs.restored != 'true' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@v7 with: name: ${{ steps.build.outputs.artifact }} path: pkg/ if-no-files-found: error retention-days: 90 - - uses: rubygems/configure-rubygems-credentials@2a7221c7c44c30ebc68eda38fa25c60b918245c7 + - uses: rubygems/configure-rubygems-credentials@main - name: Verify, publish, and finalize env: GH_TOKEN: ${{ github.token }} diff --git a/templates/release-validate.yaml.erb b/templates/release-validate.yaml.erb index 21d3da6..993ef08 100644 --- a/templates/release-validate.yaml.erb +++ b/templates/release-validate.yaml.erb @@ -15,12 +15,12 @@ jobs: name: Release validation runs-on: ubuntu-latest steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@v7 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 persist-credentials: false - - uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1 + - uses: ruby/setup-ruby@v1 with: ruby-version: <%= ruby.to_json %> bundler-cache: true diff --git a/test/bake/gem/github/setup.rb b/test/bake/gem/github/setup.rb index 19881ce..243f7fa 100644 --- a/test/bake/gem/github/setup.rb +++ b/test/bake/gem/github/setup.rb @@ -62,6 +62,25 @@ def generate expect(File.read(path)).to be == "Custom workflow\n" end + it "permits a fork checkout only after merged release inspection" do + generate + workflow = YAML.safe_load_file(File.join(root, ".github/workflows/release-publish.yaml")) + inspect = workflow.fetch("jobs").fetch("inspect") + expect(inspect.fetch("if")).to be == "github.event.pull_request.merged == true" + expect(inspect.fetch("steps").first.fetch("with")).not.to have_keys("allow-unsafe-pr-checkout", "ref") + expect(inspect.fetch("steps").last.fetch("run")).to be == "bundle exec bake gem:github:release:resolve" + publish = workflow.fetch("jobs").fetch("publish") + expect(publish.fetch("needs")).to be == "inspect" + expect(publish.fetch("if")).to be == "needs.inspect.outputs.release == 'true'" + checkout = publish.fetch("steps").first.fetch("with") + expect(checkout.fetch("ref")).to be == "${{ needs.inspect.outputs.commit }}" + expect(checkout.fetch("allow-unsafe-pr-checkout")).to be == true + %w[prepare validate].each do |name| + workflow = File.read(File.join(root, ".github/workflows/release-#{name}.yaml")) + expect(workflow).not.to be(:include?, "allow-unsafe-pr-checkout") + end + end + it "attests both the gem and its source receipt with native provenance" do generate workflow = YAML.safe_load_file(File.join(root, ".github/workflows/release-publish.yaml"))