diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2de7de..a464d65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: with: persist-credentials: false - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 cache: npm @@ -77,7 +77,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ matrix.node-version }} cache: npm @@ -118,7 +118,7 @@ jobs: with: persist-credentials: false - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 cache: npm @@ -138,7 +138,7 @@ jobs: fi echo "sha256=$(sha256sum "${tarballs[0]}" | awk '{ print $1 }')" >> "$GITHUB_OUTPUT" - name: Upload the packed artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ci-npm-package path: ci-artifacts/*.tgz @@ -152,7 +152,7 @@ jobs: timeout-minutes: 10 steps: - name: Download the packed artifact by name - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: ci-npm-package path: ci-artifacts @@ -179,7 +179,7 @@ jobs: with: persist-credentials: false - name: Set up Node.js 22 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 22.23.4 cache: npm @@ -202,7 +202,7 @@ jobs: with: persist-credentials: false - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 cache: npm @@ -222,7 +222,7 @@ jobs: with: persist-credentials: false - name: Set up Node.js 26 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 26.12.1 cache: npm @@ -249,7 +249,7 @@ jobs: with: persist-credentials: false - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 cache: npm diff --git a/.github/workflows/live-smoke.yml b/.github/workflows/live-smoke.yml index f741712..862b06b 100644 --- a/.github/workflows/live-smoke.yml +++ b/.github/workflows/live-smoke.yml @@ -29,7 +29,7 @@ jobs: with: persist-credentials: false - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 cache: npm diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ae1be7c..db695ea 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -64,7 +64,7 @@ jobs: persist-credentials: false ref: ${{ github.event.workflow_run.head_sha }} - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 cache: npm @@ -107,7 +107,7 @@ jobs: run: npm ci --ignore-scripts - name: Download the exact Release Please result if: steps.result.outputs.has-result == 'true' - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: release-please-result-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} path: ${{ runner.temp }}/release-please-result @@ -432,7 +432,7 @@ jobs: "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RELEASE_RUN_ID}" \ > "$SOURCE_RUN_FILE" - name: Download the exact Release Please result - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: release-please-result-${{ env.SOURCE_RELEASE_RUN_ID }}-${{ env.SOURCE_RELEASE_RUN_ATTEMPT }} path: ${{ runner.temp }}/release-please-result @@ -542,7 +542,7 @@ jobs: ); EOF - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 cache: npm @@ -655,7 +655,7 @@ jobs: set -euo pipefail echo "name=npm-package-${{ steps.version.outputs.version }}-${{ github.run_id }}-${{ github.run_attempt }}" >> "$GITHUB_OUTPUT" - name: Upload the verified release artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ steps.artifact-name.outputs.name }} path: ${{ steps.pack.outputs.tarball }} @@ -693,7 +693,7 @@ jobs: persist-credentials: false ref: ${{ needs.verify.outputs.release-commit }} - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 cache: npm @@ -753,7 +753,7 @@ jobs: persist-credentials: false ref: ${{ needs.verify.outputs.release-commit }} - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 package-manager-cache: false @@ -761,7 +761,7 @@ jobs: - name: Use a Trusted Publishing-capable npm CLI run: npm install --global npm@11.12.1 - name: Download the verified release artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: ${{ needs.verify.outputs.artifact-name }} path: release-artifacts diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 13690a2..a18f2c4 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -62,7 +62,7 @@ jobs: exit 1 fi - name: Set up Node.js 24 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24.22.1 cache: npm @@ -1205,7 +1205,7 @@ jobs: EOF - name: Upload the exact Release Please result if: steps.preflight.outputs.mode == 'release' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: release-please-result-${{ github.run_id }}-${{ github.run_attempt }} path: release-please-result/result.json diff --git a/RELEASING.md b/RELEASING.md index 7437d6e..860e3b6 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -182,6 +182,17 @@ Responses streams. It never contacts the production API. validate every workflow. It is not a GitHub Actions emulator and does not prove that any workflow executed remotely. +Action updates that touch `publish.yml` require a coordinated review of the +upstream commits and the resulting executable workflow. After that review, +update `PUBLISH_WORKFLOW_CONTRACT_SHA256` in +`scripts/release-workflow-validation.mjs` in the same pull request as the action +pins, then run `npm run verify`. The fingerprint is the SHA-256 of +`JSON.stringify(parse(publishYaml))`, using the repository's `yaml` package. +Dependabot cannot perform this review or update the fingerprint. Repeated +rebases or reruns of an action-only pull request will therefore keep failing; +finish the coordinated update before merging it. Preserve the negative tests +for unreviewed action commits, permissions, gates, and publication commands. + ## Version and dist-tag agreement `package.json` is the sole candidate-version authority; validation must not diff --git a/scripts/release-workflow-validation.mjs b/scripts/release-workflow-validation.mjs index e34e859..e7efbce 100644 --- a/scripts/release-workflow-validation.mjs +++ b/scripts/release-workflow-validation.mjs @@ -18,7 +18,7 @@ const RELEASE_WORKFLOW_STEP = "Run Release Please"; const PUBLISH_OPERATION = "release"; const NPM_TAG_POLICY_ID = 55718965; const PUBLISH_WORKFLOW_CONTRACT_SHA256 = - "379ba8678416ff96d3bcf220a23e8602d2b55890164b2847331def10b4e99693"; + "98910174abd3fc5a1d7e8c0cb64efb717960d54557e9799beea39a6228ef4bac"; const PUBLISH_HANDOFF_IF = "vars.RELEASE_PLEASE_ENABLED == 'true' && github.event_name == 'workflow_run' && github.run_attempt == 1 && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main'"; const PUBLISH_RESULT_IF = "steps.result.outputs.has-result == 'true'"; diff --git a/tests/release-workflow-validation.test.mjs b/tests/release-workflow-validation.test.mjs index bbc3eb4..765ff8a 100644 --- a/tests/release-workflow-validation.test.mjs +++ b/tests/release-workflow-validation.test.mjs @@ -368,6 +368,21 @@ describe("Publish workflow dispatch contract", () => { }); }); + it.each([ + "actions/setup-node", + "actions/upload-artifact", + "actions/download-artifact", + ])("rejects an unreviewed %s commit", (action) => { + const workflow = workflowContract(); + const step = Object.values(workflow.jobs) + .flatMap((job) => job.steps) + .find((candidate) => candidate.uses?.startsWith(`${action}@`)); + step.uses = `${action}@${"a".repeat(40)}`; + expect(() => validatePublishWorkflowContract(workflow)).toThrow( + /Publish executable definition digest/, + ); + }); + it.each([ [ "optional control input",