Skip to content

Add OIDC trusted publishing + staged npm releases via GitHub Actions#363

Merged
patocallaghan merged 4 commits into
masterfrom
patoc/oidc-staged-publishing
Jun 23, 2026
Merged

Add OIDC trusted publishing + staged npm releases via GitHub Actions#363
patocallaghan merged 4 commits into
masterfrom
patoc/oidc-staged-publishing

Conversation

@patocallaghan

Copy link
Copy Markdown
Member

Why?

Moves npm publishing onto short-lived, per-run OIDC credentials with a human approval step, removing the need for a stored long-lived npm token.

How?

Adds a release-triggered GitHub Actions workflow that authenticates to npm via OIDC (no token) and uses npm's staged publishing, so each release is queued for a maintainer to approve before it goes live.

Generated with Claude Code

patocallaghan and others added 3 commits June 10, 2026 11:04
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d runtime

Apply the fixes validated on the passport-intercom and cli publish
workflows:
- verify: use fetch-depth: 0 and drop the manual `git fetch --depth=1`,
  so the default-branch ancestry check has the history it needs (the
  double-shallow version could only pass when the tag was the branch tip)
- add a top-level concurrency group so overlapping releases serialize
  instead of racing for a dist-tag
- add timeout-minutes: 15 to stage-publish

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

socket-security Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedgithub/​actions/​checkout@​de0fac2e4500dabe0009e67214ff5f5447ce83dd99100100100100

View full report

@patocallaghan patocallaghan marked this pull request as ready for review June 12, 2026 09:47
@imSzukala

Copy link
Copy Markdown
Contributor

Review

Ported the RN OIDC staged-publishing workflow (intercom/intercom-react-native#360) faithfully on the security scaffolding — SHA-pinned actions, env-var indirection for the release tag, least-privilege id-token: write on the publish job only, ancestry gate, concurrency, and staged publish all look good. But there are two blockers stemming from the fact that this repo's layout differs from the RN repo.

🔴 Blocker 1 — Workflow runs from repo root, but there is no root package.json

The publishable package (cordova-plugin-intercom) lives in intercom-plugin/; there is no root package.json. So:

  • verifynode -p "require('./package.json').version" fails immediately.
  • stage-publishnpm stage publish from root has no package to publish.

Both the version assert and the publish step need to run inside intercom-plugin/ (e.g. working-directory: ./intercom-plugin, and read ./intercom-plugin/package.json). RN's package is at the repo root, so it didn't need this — this is exactly the "editable region" the CAVEAT header flags as unresolved. As written the workflow cannot succeed.

🔴 Blocker 2 — The CircleCI publish job is not removed → double publish

circle.yml is untouched, and its publish job fires on any tag matching /[0-9]+(\.[0-9]+)+/. Publishing a GitHub Release creates that tag, so the existing CircleCI token-based publish and the new GH Actions OIDC publish both fire on every release — they race on the dist-tag, and it defeats the purpose of removing the stored NPM_TOKEN.

RN's PR deleted its release-to-npm job and its workflow wiring from CircleCI. This PR needs to remove the publish job + its workflow entry from circle.yml.

🟠 Issue 3 — Missing TOCTOU / tag-mutability hardening that RN added

RN's final commit (6d565ef) had validate emit the resolved, ancestry-checked SHA and had downstream jobs check out that SHA rather than re-resolving the mutable tag. Here, stage-publish does a fresh checkout with no ref:, re-resolving the tag independently of what verify validated — the window RN deliberately closed. Suggest mirroring RN: have verify output sha, and stage-publish check out ${{ needs.verify.outputs.sha }}.

🟡 Minor

  • No build/test job — acceptable here. The plugin ships source as-is, has no prepare/build script (package.json has no scripts), matching the old CircleCI behaviour (npm publish only). RN needed lint/ts/test/build; Cordova legitimately doesn't.
  • Prerelease dist-tag is next vs RN's beta — fine if intentional.
  • OIDC trusted-publisher config on npmjs.org must target workflow filename publish.yml and the intercom-plugin package (operational prerequisite, not in the diff).
  • The large CAVEAT header should be resolved and removed before merge rather than shipped — its presence signals the workflow is still a template.

Bottom line

Not mergeable as-is. Fix the two blockers — (1) wire every package step to intercom-plugin/, and (2) delete the CircleCI publish job so releases don't publish twice — and ideally fold in RN's SHA-pinning between jobs.

~ Automated via Claude

…sh, pin SHA between jobs

- Run version assert, dist-tag resolve, and `npm stage publish` inside
  intercom-plugin/ (the publishable package lives there; no root package.json).
- Remove the CircleCI token-based publish job and its workflow wiring so
  releases no longer double-publish / race on the dist-tag.
- verify now outputs the ancestry-checked SHA; stage-publish checks out that
  exact SHA instead of re-resolving the mutable tag (closes the TOCTOU window).
- Remove the resolved CAVEAT template header.

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

Copy link
Copy Markdown
Member Author

@imSzukala Thanks for the review. I've updated per your feedback 🙇‍♂️

@imSzukala imSzukala left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving — this faithfully mirrors the merged RN reference (intercom-react-native#360) and correctly adapts it to this repo's structure. I verified the structural claims against the tree.

Verified ✅

  • intercom-plugin/package.json has no scripts → no build/prepare step needed
  • No lockfile in intercom-plugin/, and no root package.json (hence working-directory: ./intercom-plugin for the version assert)
  • Old circle.yml publish job ran only npm publish → behavior preserved, no build regression
  • No .npmignore/files field → publishes the whole dir, same as before
  • Tags are plain numeric, so ${RELEASE_TAG#v} is a harmless defensive strip

Parity with #360

Pinned action SHAs, least-privilege permissions (id-token: write only on the publish job), serialized concurrency with cancel-in-progress: false, default-branch ancestry check + SHA pinning (TOCTOU guard), persist-credentials: false, package-manager-cache: false, npm 11.15.0 + npm stage publish --tag, and the prerelease dist-tag guard — all match.

The one intentional divergence — omitting the test job that RN runs — is justified: this package has no JS test suite or build, ships source as-is, and the old CircleCI job did the same.

Minor, non-blocking

  1. RN runs a version-format regex before the tag-vs-package equality check; here it's equality only. The equality check is the part that matters; the regex is optional defense-in-depth.
  2. RN pins ref: ${{ github.event.release.tag_name }} on the validate checkout; here the verify job relies on the default github.sha. Equivalent for release events — adding the explicit ref would just match RN.
  3. The human-approval gate is entirely npm-side (npm stage publish stages; a maintainer promotes on npmjs.com) — same as RN. Worth confirming the npm org has staged publishing + the OIDC trusted-publisher config set up for cordova-plugin-intercom before the first release run.

~ Automated via Claude

@patocallaghan patocallaghan merged commit 4af8420 into master Jun 23, 2026
5 checks passed
@patocallaghan patocallaghan deleted the patoc/oidc-staged-publishing branch June 23, 2026 08:58
@patocallaghan

Copy link
Copy Markdown
Member Author

Thanks @imSzukala. I've enabled the OIDC + staged publishing on the npm side now.

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.

2 participants