diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index b945017cc1..c34dd6e014 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -23,11 +23,12 @@ on: - next - historic publish_mode: - description: "staged = npm stage publish (owner approves with 2FA after the staged boot-verify); direct = legacy immediate publish (emergency fallback only)" + description: "auto = publish through npm Trusted Publishing (OIDC, no token, no 2FA prompt — the default); staged = npm stage publish (owner approves with 2FA); direct = legacy token publish (emergency fallback only)" required: false - default: "staged" + default: "auto" type: choice options: + - auto - staged - direct workflow_call: @@ -407,8 +408,34 @@ jobs: fi npm --version + # Trusted Publishing (OIDC): npm mints a short-lived credential for THIS run from + # GitHub's id-token — no NPM_TOKEN secret, no 2FA prompt, provenance included, and + # it is the bypass npm sanctions now that tokens which skip 2FA are being retired + # (gh.io/npm-gat-bypass2fa-deprecation). Requires the package's Trusted Publisher to + # be configured on npmjs.com (owner: diegosouzapw/OmniRoute, workflow + # npm-publish.yml) and a github-hosted runner — which is why this job exists. + # Without that configuration `npm publish` fails with ENEEDAUTH: re-dispatch with + # publish_mode=staged or direct. Automatic publishing was the flow up to v3.8.48; + # v3.8.49 moved to staged (WS1.3) to keep a leaked token from publishing alone — + # OIDC gives the same guarantee without the manual approve. + - name: Publish to npm (Trusted Publishing / OIDC — automatic) + if: github.event_name != 'workflow_dispatch' || inputs.publish_mode == 'auto' + env: + VERSION: ${{ needs.publish.outputs.version }} + TAG: ${{ needs.publish.outputs.tag }} + run: | + set -euo pipefail + TARBALL="omniroute-${VERSION}.tgz" + test -f "$TARBALL" || { echo "tarball $TARBALL did not arrive from the publish job" >&2; ls -la; exit 1; } + # Deliberately NO NODE_AUTH_TOKEN in this step: npm >= 11.5 detects the GitHub + # OIDC token itself. Always pass --tag explicitly (defense in depth: an older + # VERSION can never claim `@latest`). + npm publish "$TARBALL" --provenance --access public --tag "$TAG" --ignore-scripts + echo "✅ Published omniroute@$VERSION (dist-tag=$TAG) via Trusted Publishing" + - name: Publish to npm (staged — owner approves with 2FA) - if: github.event_name != 'workflow_dispatch' || inputs.publish_mode != 'direct' + # Only on an explicit request now: Trusted Publishing below is the default. + if: github.event_name == 'workflow_dispatch' && inputs.publish_mode == 'staged' env: VERSION: ${{ needs.publish.outputs.version }} TAG: ${{ needs.publish.outputs.tag }} diff --git a/changelog.d/features/npm-trusted-publishing-oidc.md b/changelog.d/features/npm-trusted-publishing-oidc.md new file mode 100644 index 0000000000..8452c0c0b3 --- /dev/null +++ b/changelog.d/features/npm-trusted-publishing-oidc.md @@ -0,0 +1,4 @@ +- The npm publish is automatic again, through npm Trusted Publishing (OIDC): the hosted + `stage-npm` job publishes with a short-lived credential minted from GitHub's id-token — + no `NPM_TOKEN`, no 2FA prompt, provenance attached. `publish_mode=staged` (owner + approves with 2FA) and `direct` (token) remain available on `workflow_dispatch`. diff --git a/docs/ops/RELEASE_CHECKLIST.md b/docs/ops/RELEASE_CHECKLIST.md index dfa96d53ee..53fa6bb733 100644 --- a/docs/ops/RELEASE_CHECKLIST.md +++ b/docs/ops/RELEASE_CHECKLIST.md @@ -1,12 +1,12 @@ --- title: "Release Checklist" -version: 3.8.40 -lastUpdated: 2026-06-28 +version: 3.8.51 +lastUpdated: 2026-08-28 --- # Release Checklist -> **Last updated:** 2026-06-28 — v3.8.40 +> **Last updated:** 2026-08-28 — v3.8.51 > Streamlined release flow that leverages Claude Code skills for automation. > > **Keep the queue/branch green between releases:** see [RELEASE_GREEN.md](./RELEASE_GREEN.md) @@ -37,7 +37,21 @@ npm run test:e2e # optional but recommended /capture-release-evidences-cc ``` -## npm Staged Publishing (default since v3.8.49 — WS1.3/D2) +## npm Trusted Publishing (default since v3.8.51) — staged on request, direct as fallback + +`npm-publish.yml` publishes through **npm Trusted Publishing (OIDC)** by default: the +`stage-npm` job (github-hosted) exchanges GitHub's id-token for a short-lived npm +credential for that run — no long-lived npm token in the repository secrets, no 2FA prompt, provenance attached. +That is the bypass npm sanctions now that tokens which skip 2FA are being retired; +it restores the fully automatic flow the project had up to v3.8.48 while keeping the +WS1.3 guarantee (a leaked token cannot publish alone — there is no token). + +**One-time setup (owner):** npmjs.com → package `omniroute` → Settings → *Trusted +Publisher* → GitHub: owner `diegosouzapw`, repo `OmniRoute`, workflow `npm-publish.yml` +(environment: none). Until that exists, the automatic step fails with `ENEEDAUTH`: +re-dispatch with `publish_mode=staged` (below) or `direct`. + +### Staged publishing (on request — `publish_mode=staged`) The npm-publish workflow no longer publishes directly: it boots the packed tarball (`check:pack-boot`) and then runs `npm stage publish` — the exact bytes are parked on