Repository navigation
CI: publish to npm via trusted publishing instead of NPM_TOKEN - #95
Merged
Merged
Conversation
The 1.9.0 release failed with `E404 Not Found - PUT` from the registry,
which is npm's response to a write from an unauthorized caller — the
NPM_TOKEN secret is expired or no longer has write access to the scope.
Replace the stored token with npm trusted publishing (OIDC). npm
exchanges the GitHub-issued OIDC token for a short-lived, run-scoped
publish credential, so there is no long-lived secret left to expire.
- Bump Node to 24: npm >= 11.5.1 is required for OIDC publishing and
Node 22 still ships npm 10. Node 24.20.0 ships npm 11.19.0.
- Bump actions/checkout and actions/setup-node to v7, clearing the
Node 20 runtime deprecation warnings.
- Drop NPM_TOKEN. changesets/action has supported OIDC since v1.7.0:
with no NPM_TOKEN and the OIDC env present it skips writing .npmrc
and lets npm authenticate itself.
- Drop the now-redundant GITHUB_TOKEN env var. The action's
`github-token` input already defaults to `${{ github.token }}`, the
same per-run token, in both v1 and v2. The `permissions` block is
what actually scopes it.
This also makes the pre-existing `id-token: write` permission
meaningful — provenance attestations are now generated automatically,
which the comment claimed but nothing enabled.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jbnNY5DdggoZnmjnA8fmT
11bit
force-pushed
the
ci/npm-trusted-publishing
branch
from
August 27, 2026 09:47
9bb18c2 to
915a7d2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The 1.9.0 release run failed at the publish step:
The package exists (
1.8.0islatest, 17 versions published), so a 404 on aPUTis not "package missing" — npm returns 404 rather than 403 on writes to avoid leaking package existence to unauthorized callers. The secret reached the runner (No user .npmrc file found, creating one with NPM_TOKEN used as auth token), soNPM_TOKENis set but expired or no longer has write access to the@imgproxyscope.Rather than rotate a secret that will expire again, this switches to npm trusted publishing: npm exchanges the GitHub-issued OIDC token for a short-lived credential scoped to a single workflow run. Nothing publishable is left at rest in repo secrets.
What changed
NPM_TOKEN.changesets/actionhas supported OIDC since v1.7.0 — with noNPM_TOKENbut the OIDC env present, it skips writing.npmrcand lets npm authenticate itself (source). No migration tochangesets/action@v2is needed, which is good: v2 requires Changesets v3 and we're on v2.28.actions/checkout@v4→@v7,actions/setup-node@v3→@v7. Clears the Node 20 runtime deprecation warnings on the run. Neither major's breaking changes apply here (checkout v7 restricts fork checkout underpull_request_target/workflow_run; setup-node v5/v6 changed automatic-cache defaults, and we setcache: "npm"explicitly).GITHUB_TOKENenv var, which empties the step'senv:block entirely. This is tidiness, not a security change: unlikeNPM_TOKEN,secrets.GITHUB_TOKENis not a stored credential — GitHub mints it per run and revokes it at job end. The action still needs a token to open the version PR, push the release commit and tag, and create the release; it just gets it from thegithub-tokeninput, which defaults to${{ github.token }}in both v1 and v2 — the same token. Thepermissions:block is what actually scopes what it can do.id-token: writebecomes meaningful. The permission and its "needed for provenance" comment were already there, but nothing enabled provenance — there's no--provenanceflag orpublishConfig.provenance. Under trusted publishing from a public repo, provenance attestations are generated automatically.Required before merging
On npmjs.com →
@imgproxy/imgproxy-js-core→ Settings → Trusted Publisher → GitHub Actions:imgproxyimgproxy-js-corepublish.ymlnpm publishThis is one time. It doesn't expire, isn't tied to the person who set it up, and requires nothing per release. It only needs revisiting if the workflow filename, repo name, or org changes — npm matches those claims from the OIDC token. Leave the environment field empty: adding one would gate every release on a manual approval, and npm warns approval delays can cause OIDC timing issues.
Optional hardening afterwards, once a release has gone through: set the package to "Require two-factor authentication and disallow tokens", which disables token-based publishing entirely while trusted publishing keeps working.
Effect on 1.9.0
f0c8031already landed the version bump —package.jsonis at 1.9.0, changesets are consumed,CHANGELOG.mdis written — but nothing was published and nov1.9.0tag or GitHub release was created. There's deliberately no changeset in this PR, so merging it pushes tomain, and the publish job will find no changesets, notice 1.9.0 is unpublished, and publish it with provenance plus the tag and release. No token rotation needed at all if the trusted publisher is registered first.Verification
The publish workflow only runs on
main, so its build step wouldn't be exercised until merge. I ran it locally against a clean clone on Node 24.20.0 / npm 11.19.0:npm ciwarns that esbuild's and fsevents' install scripts weren't run — that's npm 11's new default script gating, not a config difference, and it's harmless here since Vite 6 resolves esbuild's binary through optional dependencies. The build passes.Not in scope
.github/workflows/ci.ymlstill usesactions/checkout@v3and Node 18 and emits the same deprecation warnings. I left it alone because bumping its Node version changes what the test suite actually runs against — worth a separate PR deciding which versions to support.🤖 Generated with Claude Code
https://claude.ai/code/session_015jbnNY5DdggoZnmjnA8fmT