From d3df53833dbc9c321999e4ef7077eb7dce75c6c8 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Thu, 6 Aug 2026 05:16:31 +0000 Subject: [PATCH] ci: publish by trusted publishing again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Back to OIDC: npm trades the short-lived token GitHub mints for this specific workflow run for permission to publish, so there is no long-lived credential in the repository at all. This is the version from 18d9084, restored, plus the failure signature we did not have the first time. When the trusted publisher is not registered there is no credential in the run, and npm reports that as: npm error code E404 ... could not be found or you do not have permission which names neither OIDC nor trusted publishing and reads as though the package does not exist. The header now says what it actually means, because that cost a release to work out. The E422 that stopped v0.24.1 is unrelated and already fixed: package.json now carries the `repository` field provenance is checked against. Still requires the publisher to be registered on npmjs.com against this file's name, `publish.yml`. Nothing in the repository can verify that — the registry does not expose it — so the next release is the proof. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/publish.yml | 68 +++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 13390a2..a682669 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,15 +10,26 @@ # that failed on something transient; it is safe because a version already on # the registry is skipped rather than attempted. # -# Authenticates with a stored npm automation token, in the repository secret -# `NPM_TOKEN`. Trusted publishing (OIDC) would avoid the stored credential, but -# it needs a trusted publisher registered against this workflow's filename on -# npmjs.com, which can only be done through the web UI — and until that exists -# npm rejects the publish as E404/no-permission, which is how v0.24.1 failed. +# Authenticates by trusted publishing (OIDC) rather than a stored token: npm +# trades the short-lived token GitHub mints for this specific workflow run for +# permission to publish, so there is no long-lived credential in the repository +# to leak, rotate or forget. # -# Worth knowing when this is next revisited: npm is restricting tokens that -# bypass 2FA for direct publishing, so the token path has a horizon. -# https://gh.io/npm-gat-bypass2fa-deprecation +# The other half of that trust lives on npmjs.com, under the package's Trusted +# Publisher settings, and it is pinned to the *filename* of this workflow. +# Renaming this file silently breaks publishing — npm will refuse the exchange +# because the run no longer matches what was configured. +# +# What that looks like when it goes wrong, since the error names neither OIDC +# nor trusted publishing: +# +# npm error code E404 +# npm error 404 Not Found - PUT https://registry.npmjs.org/moshcode +# npm error 404 ... could not be found or you do not have permission +# +# There is no credential at all in that case, and npm reports it as if the +# package did not exist. If you see it, the publisher is not registered, or is +# registered against a different workflow filename. name: publish on: @@ -28,9 +39,9 @@ on: permissions: contents: read - # Still needed with token auth: provenance is signed with a short-lived OIDC - # token even though the publish itself authenticates with NPM_TOKEN. Without - # it `--provenance` fails. + # The whole basis of the exchange: this is what lets the run mint the OIDC + # token npm authenticates against. Without it there is no credential at all + # and publishing fails outright. id-token: write jobs: @@ -51,6 +62,22 @@ jobs: cache: pnpm registry-url: https://registry.npmjs.org + # Node 22 bundles npm 10, which predates trusted publishing and would fall + # back to looking for a token that no longer exists — an auth failure that + # reads as a credential problem rather than a version one. 11.5.1 is the + # floor; the check below says so plainly if that ever regresses. + - name: Install an npm that understands trusted publishing + run: | + npm install -g npm@latest + VERSION="$(npm --version)" + MINIMUM=11.5.1 + echo "npm $VERSION" + # Lowest of the two must be the minimum, or this npm is older than it. + if [ "$(printf '%s\n%s\n' "$MINIMUM" "$VERSION" | sort -V | head -n1)" != "$MINIMUM" ]; then + echo "::error::npm $VERSION cannot use trusted publishing — $MINIMUM or later is required" + exit 1 + fi + - run: pnpm install --frozen-lockfile # Publishing is the one action here that cannot be taken back — npm will @@ -95,24 +122,11 @@ jobs: echo "already=false" >> "$GITHUB_OUTPUT" fi - # Said plainly here, rather than as the E404/no-permission npm otherwise - # returns partway through a release — an error that reads as "the package - # does not exist" rather than "there is no credential". - - name: Require an npm token - if: steps.published.outputs.already == 'false' - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - if [ -z "$NPM_TOKEN" ]; then - echo "::error::NPM_TOKEN is not set — add an npm automation token as a repository secret named NPM_TOKEN" - exit 1 - fi - + # No token, and no `--provenance` either: publishing through trusted + # publishing generates and attaches the attestation on its own. - name: Publish if: steps.published.outputs.already == 'false' - run: npm publish --access public --provenance - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish --access public - name: Confirm the registry has it if: steps.published.outputs.already == 'false'