From e3ed2d1934d8125080023b7b05f6402a7345ab24 Mon Sep 17 00:00:00 2001 From: Pat O'Callaghan Date: Wed, 10 Jun 2026 16:31:44 +0100 Subject: [PATCH 1/2] Harden publish workflow: fix ancestry guard, serialize releases, bound runtime Follow-up to the merged staged-publishing workflow. Apply three fixes validated on the sibling packages' workflows: - verify: use fetch-depth: 0 and drop the manual `git fetch --depth=1`. The double-shallow checkout+fetch left the tag commit and the default branch tip with no shared local history, so the ancestry guard could only pass when the tag was exactly the branch tip (failing closed on legitimate releases). A full-depth checkout populates the remote ref with the history merge-base needs. - add a top-level concurrency group so overlapping releases serialize instead of racing for a dist-tag. - add timeout-minutes: 15 to stage-publish so a hung publish fails fast. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b9267d8b..19f6d09f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,12 +14,18 @@ on: permissions: contents: read # workflow default (least privilege); only stage-publish also needs id-token, granted on that job +concurrency: + group: publish-${{ github.workflow }} # serialize all publish runs; never two staged releases racing for a dist-tag + cancel-in-progress: false # queue, don't cancel: killing a half-done `npm stage publish` is the torn state we're avoiding + jobs: verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: { persist-credentials: false } + with: + persist-credentials: false + fetch-depth: 0 # full history so origin/ ancestry is computable; checkout fetches authenticated before stripping creds - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: '.nvmrc' # pin >= 22.14.0 @@ -35,13 +41,13 @@ jobs: RELEASE_TAG: ${{ github.event.release.tag_name }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} run: | - git fetch origin "$DEFAULT_BRANCH" --depth=1 git merge-base --is-ancestor "$GITHUB_SHA" "origin/$DEFAULT_BRANCH" \ || { echo "release $RELEASE_TAG not reachable from $DEFAULT_BRANCH — refusing"; exit 1; } stage-publish: needs: verify runs-on: ubuntu-latest + timeout-minutes: 15 # bound a hung publish instead of running to the 6h default permissions: contents: read id-token: write # OIDC trusted publishing: only this job mints the token From 05d4c4d66bfa8cab51a7848f1e9444d15e2ea357 Mon Sep 17 00:00:00 2001 From: Pat O'Callaghan Date: Wed, 10 Jun 2026 17:34:57 +0100 Subject: [PATCH 2/2] Trim verbose comments on the hardening changes Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 19f6d09f..391106d4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,8 +15,8 @@ permissions: contents: read # workflow default (least privilege); only stage-publish also needs id-token, granted on that job concurrency: - group: publish-${{ github.workflow }} # serialize all publish runs; never two staged releases racing for a dist-tag - cancel-in-progress: false # queue, don't cancel: killing a half-done `npm stage publish` is the torn state we're avoiding + group: publish-${{ github.workflow }} # serialize publishes; no dist-tag races + cancel-in-progress: false # queue, don't kill an in-flight publish jobs: verify: @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - fetch-depth: 0 # full history so origin/ ancestry is computable; checkout fetches authenticated before stripping creds + fetch-depth: 0 # full history for the ancestry check below - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: '.nvmrc' # pin >= 22.14.0 @@ -47,7 +47,7 @@ jobs: stage-publish: needs: verify runs-on: ubuntu-latest - timeout-minutes: 15 # bound a hung publish instead of running to the 6h default + timeout-minutes: 15 # cap a hung publish permissions: contents: read id-token: write # OIDC trusted publishing: only this job mints the token