Skip to content

Add staged OIDC publish workflow#524

Open
patocallaghan wants to merge 4 commits into
masterfrom
patoc/oidc-staged-publishing
Open

Add staged OIDC publish workflow#524
patocallaghan wants to merge 4 commits into
masterfrom
patoc/oidc-staged-publishing

Conversation

@patocallaghan

Copy link
Copy Markdown
Member

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

patocallaghan and others added 2 commits June 12, 2026 13:21
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>
@patocallaghan patocallaghan marked this pull request as ready for review June 17, 2026 15:03
Pin to 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 (v7.0.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@VedranZoricic VedranZoricic left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 genuineactions/checkout@9c091bb… really is v7.0.0 and actions/setup-node@48b55a0… really is v6.4.0 (resolved the dereferenced tag commits via git ls-remote …^{}, not just the tag objects).
  • npm stage publish is used correctly — staging needs no 2FA; the 2FA gate is npm stage approve, which matches the "CI stages → maintainer promotes" design.
  • No leftover long-lived-token pathpublish.yml is the only publishing workflow on the branch, and ci.yml has 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.

~ Automated via Claude

Comment thread .github/workflows/publish.yml Outdated
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" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .github/workflows/publish.yml
- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants