diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca8ff7a10f..99e3888d4f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: fetch-depth: 0 + persist-credentials: false - name: Verify dispatched SHA env: @@ -453,8 +454,10 @@ jobs: "${render_args[@]}" if [ -z "$existing_tag_sha" ]; then - git tag "$release_tag" "$GITHUB_SHA" - git push origin "refs/tags/${release_tag}" + gh api --method POST \ + "repos/${GITHUB_REPOSITORY}/git/refs" \ + -f ref="refs/tags/${release_tag}" \ + -f sha="$GITHUB_SHA" >/dev/null fi gh release create "$release_tag" --target "$GITHUB_SHA" --title "$release_tag" \ diff --git a/tests/ci-workflows.test.ts b/tests/ci-workflows.test.ts index f8d67f42b3..3eaba81ede 100644 --- a/tests/ci-workflows.test.ts +++ b/tests/ci-workflows.test.ts @@ -737,8 +737,17 @@ describe("GitHub Actions hardening", () => { } expect(createStep).not.toContain("set +e\n pr_notes"); expect(createStep.indexOf("gh api")).toBeGreaterThan(-1); - expect(createStep.indexOf('git tag "$release_tag"')).toBeGreaterThan(-1); - expect(createStep.indexOf("gh api")).toBeLessThan(createStep.indexOf('git tag "$release_tag"')); + const tagRefApi = '"repos/${GITHUB_REPOSITORY}/git/refs"'; + expect(createStep).toContain("gh api --method POST"); + expect(createStep).toContain(tagRefApi); + expect(createStep).toContain('-f ref="refs/tags/${release_tag}"'); + expect(createStep).toContain('-f sha="$GITHUB_SHA" >/dev/null'); + expect(createStep).not.toContain('git tag "$release_tag"'); + expect(createStep).not.toContain('git push origin "refs/tags/${release_tag}"'); + + // Release notes must be fully rendered before the remote tag is created. + expect(createStep.indexOf('"${render_args[@]}"')) + .toBeLessThan(createStep.indexOf(tagRefApi)); // The notes baseline must read the FULL tag set, not `--merged HEAD`: stable // tags live on main's lineage, which the preview branch does not carry, and a // trailing same-core preview must not hide the stable from the range