From 4f01a513d4080232b4a535aa79b86d0a2dfb29d7 Mon Sep 17 00:00:00 2001 From: Joichiro Hayashi Date: Thu, 24 Sep 2026 20:19:42 +0900 Subject: [PATCH] ci(release): create release tags with a deploy key - Only deploy keys bypass the `tags` ruleset, because a personal repository cannot list GitHub Actions as a bypass actor - Key in the `release` environment (main only), loaded into an ssh-agent for the tag step alone --- .github/workflows/release.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed7d8b4..e7a2c19 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,8 +61,9 @@ jobs: needs: verify if: needs.verify.outputs.due == 'true' runs-on: ubuntu-latest + environment: release # holds the deploy key that creates the tag permissions: - contents: write # the tag and the release are created below + contents: write # the release is created below id-token: write # npm trusted publishing and provenance env: TAG: ${{ needs.verify.outputs.tag }} @@ -95,11 +96,19 @@ jobs: - run: vp run ts-compatibility # Tags are immutable, so tagging only after the checks pass keeps a broken commit from burning - # its version. + # its version. Only deploy keys may create tags (the `tags` ruleset), so the push goes over SSH + # with the `release` environment's key; the agent holding it ends with this step, and the key + # never hits disk. - name: Create tag - run: gh api "repos/$GITHUB_REPOSITORY/git/refs" -f ref="refs/tags/$TAG" -f sha="$GITHUB_SHA" + run: | + eval "$(ssh-agent -s)" > /dev/null + trap 'ssh-agent -k > /dev/null' EXIT + ssh-add -q - <<< "$RELEASE_TAG_KEY" + curl -fsS https://api.github.com/meta | jq -r '.ssh_keys[] | "github.com \(.)"' > "$RUNNER_TEMP/known_hosts" + git -c core.sshCommand="ssh -o UserKnownHostsFile=$RUNNER_TEMP/known_hosts" \ + push "git@github.com:$GITHUB_REPOSITORY.git" "$GITHUB_SHA:refs/tags/$TAG" env: - GH_TOKEN: ${{ github.token }} + RELEASE_TAG_KEY: ${{ secrets.RELEASE_TAG_KEY }} # No token: npm trusts this workflow through OIDC. --no-git-checks: the verify job already # checked this commit.