From 305f281f685ef97e888cb95dcab20ed4038b15ff Mon Sep 17 00:00:00 2001 From: John McLear Date: Fri, 22 May 2026 10:42:13 +0100 Subject: [PATCH] ci: migrate npm publish to OIDC trusted publishing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The npmpublish.yml workflow was authenticating via a stored NPM_TOKEN secret that recently started failing E404 on PUT — first observed after #981 merged at 6.1.1 (run 26279691349). Same E404 pattern that hit ep_hljs four days ago. Stored publish tokens have two failure modes that this PR removes entirely: 1. They expire / get rotated and silently break automated publishes. 2. They survive in compromised CI logs / forks long enough to be abused. OIDC trusted publishing exchanges a short-lived GitHub-issued id-token for a per-publish credential at the registry. No secret on our side, no expiry, and the resulting publish is attested with `--provenance` so the npm package page shows the signing GHA run. Removes: - "Set publishing config" step that consumed secrets.NPM_TOKEN - "Add package to etherpad organization" step (the access grant is idempotent and was set at initial publish; it doesn't need to run on every release and the OIDC credential isn't authorised for it) Adds: - `permissions: { contents: write, id-token: write }` on the publish job (contents:write was already implicit via GITHUB_TOKEN for the version-bump push; id-token:write is the OIDC enabler) - `registry-url` on setup-node so the auth header lands at npmjs.org - `--no-git-checks` on pnpm publish (skip the dirty-tree guard that would otherwise trip on the just-pushed version-bump commit) - `--provenance` for the signing attestation One-time setup required on https://www.npmjs.com/package/ueberdb2/access before this lands — Trusted Publishers → Add → Provider: GitHub Actions, Org: ether, Repo: ueberDB, Workflow: npmpublish.yml, Environment: blank. Documented at the top of the publish-npm job. Bumps the publish-job pnpm pin from 10 to 11 to match the test job; trusted publishing requires pnpm >= 10.4 either way. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/npmpublish.yml | 41 +++++++++++++++++++------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index f0ef15a0..764ab030 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -56,6 +56,18 @@ jobs: needs: - test runs-on: ubuntu-latest + # OIDC trusted publishing: npm exchanges the GitHub-issued `id-token` + # for a short-lived publish credential at the registry, so there is no + # stored NPM_TOKEN to expire or rotate. `contents: write` is needed for + # the auto-bump commit + tag push. + # + # One-time setup on https://www.npmjs.com/package/ueberdb2/access: + # Trusted Publishers → Add → Provider: GitHub Actions + # Org: ether, Repo: ueberDB, Workflow filename: npmpublish.yml + # Environment: (blank) + permissions: + contents: write + id-token: write steps: - uses: actions/checkout@v6 with: @@ -64,11 +76,12 @@ jobs: - uses: actions/setup-node@v6 with: node-version: 24 + registry-url: 'https://registry.npmjs.org' - name: Install pnpm uses: pnpm/action-setup@v5 with: - version: 10 + version: 11 run_install: false - name: Get pnpm store directory @@ -89,11 +102,6 @@ jobs: # version. - run: pnpm install --frozen-lockfile # Workaround based on https://github.com/pnpm/pnpm/issues/3141 - - name: Set publishing config - run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}" - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - name: Bump version (patch) run: | LATEST_TAG=$(git describe --tags --abbrev=0) || exit 1 @@ -107,22 +115,23 @@ jobs: git push git push --follow-tags - # `npm publish` must come after `git push` otherwise there is a race - # condition: If two PRs are merged back-to-back then master/main will be + # `pnpm publish` must come after `git push` otherwise there is a race + # condition: If two PRs are merged back-to-back then main will be # updated with the commits from the second PR before the first PR's - # workflow has a chance to push the commit generated by `npm version + # workflow has a chance to push the commit generated by `pnpm version # patch`. This causes the first PR's `git push` step to fail after the # package has already been published, which in turn will cause all future # workflow runs to fail because they will all attempt to use the same - # already-used version number. By running `npm publish` after `git push`, + # already-used version number. By running `pnpm publish` after `git push`, # back-to-back merges will cause the first merge's workflow to fail but # the second's will succeed. - name: Convert typescript and create dist folder run: pnpm run build - - run: pnpm publish - - - name: Add package to etherpad organization - run: npm access grant read-write etherpad:developers ueberdb2 - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + # pnpm 10.4+ auto-discovers the GHA OIDC token via `id-token: write` + # and exchanges it with npm for a publish credential — no NPM_TOKEN + # secret involved. `--provenance` attaches the signed-by-GitHub + # attestation that npm shows on the package page. + # `--no-git-checks` skips the "working tree dirty" guard, which would + # otherwise trip on the version-bump commit that's already been pushed. + - run: pnpm publish --no-git-checks --provenance