Add staged OIDC publish workflow#524
Conversation
Replace token-based npm publishing with OIDC trusted publishing + npm staged publishing: CI authenticates with a short-lived OIDC token (no stored npm token) and stages the release; a maintainer promotes it from the npm staging area with 2FA. The verify job asserts the Release tag matches package.json and refuses releases not reachable from the default branch. Listed in .fernignore so it is not overwritten by code generation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
verify now outputs the validated $GITHUB_SHA; stage-publish checks out that exact SHA instead of re-resolving the mutable release tag, so the commit that is published is provably the one the branch-ancestry guard approved. Mirrors the hardening already in intercom-react-native. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pin to 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 (v7.0.0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VedranZoricic
left a comment
There was a problem hiding this comment.
Reviewed for supply-chain / publish-workflow security — solid. Non-blocking comments only. ✅
Careful, security-first publish workflow; it meets Intercom's secure-github-actions standard including the stricter public-repo rules. I independently verified the parts a checklist can't:
- Action pins are genuine —
actions/checkout@9c091bb…really isv7.0.0andactions/setup-node@48b55a0…really isv6.4.0(resolved the dereferenced tag commits viagit ls-remote …^{}, not just the tag objects). npm stage publishis used correctly — staging needs no 2FA; the 2FA gate isnpm stage approve, which matches the "CI stages → maintainer promotes" design.- No leftover long-lived-token path —
publish.ymlis the only publishing workflow on the branch, andci.ymlhas no token /secrets.*/ publish references.
Particularly nice: id-token: write scoped to only the stage-publish job (least privilege for the credential that mints the publish token); verify-resolves-SHA → act-checks-out-that-SHA closes the tag-repointing TOCTOU; the default-branch ancestry gate; persist-credentials: false on both checkouts; and all ${{ }} routed through env:.
3 inline notes below: one small suggestion (a misleading comment) and two "confirm before / at first release" items. One trivial nit not worth inlining — verify has no timeout-minutes while stage-publish has 15; harmless, just inconsistent.
| RELEASE_TAG: ${{ github.event.release.tag_name }} | ||
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | ||
| run: | | ||
| git merge-base --is-ancestor "$GITHUB_SHA" "origin/$DEFAULT_BRANCH" \ |
There was a problem hiding this comment.
Confirm on the first real release (fails closed, so safe): this ancestry gate needs origin/$DEFAULT_BRANCH to resolve. actions/checkout with fetch-depth: 0 does populate refs/remotes/origin/*, so it should be fine — but if it ever isn't, every publish blocks here rather than failing open, so worth eyeballing this step in the first run's log.
- Correct the setup-node comment: staged publishing needs Node >= 22.14.0, not >= 20 (addresses review feedback on a misleading comment). - Add timeout-minutes: 5 to the verify job as a hung-step backstop, matching the bound already on stage-publish. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Why?
Standardise npm publishing on OIDC trusted publishing with a staged release step. CI authenticates with a short-lived, workflow-scoped OIDC token rather than a long-lived stored npm token, and staged publishing adds a human 2FA promotion gate before a release is visible on the registry.
How?
A release-triggered workflow verifies the tag and that the commit is on the default branch, then stages the publish via OIDC; a maintainer promotes it from the npm staging area with 2FA.
Generated with Claude Code