Skip to content

chore(release): switch npm publish to OIDC trusted publishing#21

Merged
IgorShevchik merged 3 commits into
mainfrom
chore/npm-oidc
May 28, 2026
Merged

chore(release): switch npm publish to OIDC trusted publishing#21
IgorShevchik merged 3 commits into
mainfrom
chore/npm-oidc

Conversation

@IgorShevchik

Copy link
Copy Markdown
Collaborator

Per repo owner's request: switch npm publish auth from a static NPM_AUTH_TOKEN secret to OIDC trusted publishing (npm 11.5.1+). The trusted-publisher binding (repo + workflow path → npm package) is configured on the npm side by the package maintainer; this PR is the matching repo-side wiring.

Why OIDC is better than a static token

  • No secret to rotate / leak / accidentally commit. Nothing stored in Settings → Secrets.
  • Short-lived credentials. Each publish run mints a one-shot token via OIDC; expires in minutes.
  • Tighter than a static token. npm checks the OIDC claim chain (repo, workflow filename, ref) on every publish request — a stolen token from one workflow can't be reused elsewhere.
  • --provenance attestation (already wired in PR chore: refresh toolchain, trim deps, apply multi-angle review #4) signs through the same flow — provenance is unchanged, it just no longer requires a separate auth token alongside.

What changed

.github/workflows/npm-publish.yml

  1. New step: "Upgrade npm CLI for OIDC trusted publishing" — runs npm install -g npm@latest. Node 22 LTS ships with npm 10.x, but OIDC trusted publishing requires npm 11.5.1+. pnpm 10 stays in charge of install / lint / typecheck / test / build; only the final publish step switches to npm to get first-class OIDC integration.
  2. Publish step: pnpm publish --access public --provenance --no-git-checksnpm publish --access public --provenance --ignore-scripts.
    • --ignore-scripts: the explicit Build step already produced dist/; skip the prepack hook (which would re-invoke pnpm run build) to keep the publish path lean.
    • --no-git-checks was pnpm-specific; npm has no equivalent (publishes regardless of git state by default).
  3. Removed: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}. No token needed under OIDC.

Everything else stays: id-token: write permission (load-bearing for OIDC), actions/setup-node with registry-url: https://registry.npmjs.org, all gates (lint / typecheck / test:coverage / build / docs:build), tag-vs-package.json sanity check, concurrency group, timeout-minutes: 10.

CONTRIBUTING.md

"One-time maintainer setup" step 1 rewritten:

  • Was: Secrets → NPM_AUTH_TOKEN (npm automation token with Publish scope).
  • Now: "npm OIDC trusted publisher binding" — done by package maintainer on npmjs.com/package/@bitrix24/b24rabbitmq/accessTrusted Publishers → add GitHub Actions publisher pointing at bitrix24/b24rabbitmq, workflow filename npm-publish.yml, environment empty.
  • Failure mode if skipped: Publish 🚀 (OIDC trusted publishing) step fails with npm error 401 Unauthorized referencing missing trusted-publisher binding.

CHANGELOG.md

Unreleased entry for the release-flow work (originally landed in PR #19 using token language) updated in-place to reflect the OIDC switch. Since #19 hasn't shipped yet (no release tag cut), this is a pre-release amendment, not a behavioural change to anything released.

Behavioural change

  • For maintainers: no token to manage. One npm-side binding configured once. Failed publish errors are now 401-with-binding-reference instead of ENEEDAUTH.
  • For consumers (npm users): nothing changes. Same package, same --provenance-signed tarball, same npm registry path.

Order of operations for the first v0.1 cut

The maintainer needs the npm OIDC trusted-publisher binding before the first release-please PR merges (which triggers npm-publish.yml). Suggested sequence:

  1. Owner configures npm trusted-publisher binding on npmjs.com (one-time).
  2. Owner applies the other two admin toggles documented in CONTRIBUTING (branch protection, workflow permissions).
  3. release-please opens its first release PR (auto, on next push to main).
  4. Reconcile CHANGELOG (see CONTRIBUTING "CHANGELOG reconciliation"), merge release PR.
  5. release-please creates tag v0.1.0 + GitHub Release → triggers npm-publish.yml → publishes via OIDC.

If step 1 hasn't happened yet by step 5, the publish step fails clean with the 401-binding-missing error; re-run the workflow after binding lands.

Gates

Gate Result
pnpm lint green
pnpm typecheck green
pnpm test 66/66
pnpm test:coverage 100/100/100/100
pnpm build green (17 kB ESM, unchanged)
pnpm docs:build green

Downstream consumer check

No public API change. bitrix24/app-template-automation-rules is unaffected.

What's next

Same as after PR #19: apply the 3 admin toggles (the OIDC binding replaces the NPM_AUTH_TOKEN step), merge the first release-please PR → tag v0.1.


Generated by Claude Code

claude added 3 commits May 28, 2026 08:15
Per repo owner: use OIDC, not tokens. No long-lived NPM_AUTH_TOKEN
secret stored in this repo; the workflow's id-token: write
permission lets the npm CLI exchange a short-lived GitHub OIDC
token for a one-shot publish credential. The trusted-publisher
binding (repo + workflow path -> npm package) is configured on
the npm side by the package maintainer.

Why this is better:
- No secret to rotate / leak / accidentally commit.
- Short-lived credentials reduce blast radius if a workflow run
  is somehow compromised.
- Tighter than a static token: npm checks the OIDC claim chain
  (repo, workflow, ref) on every publish request.
- --provenance attestation (already wired in PR #4) signs through
  the same flow.

Changes to .github/workflows/npm-publish.yml:
- New step "Upgrade npm CLI for OIDC trusted publishing": runs
  `npm install -g npm@latest` to ensure npm 11.5.1+ (Node 22 LTS
  ships with npm 10.x). pnpm stays in charge of install + lint +
  typecheck + test + build — only the final `publish` step
  switches to npm.
- Publish step: replaced `pnpm publish --access public
  --provenance --no-git-checks` with `npm publish --access public
  --provenance --ignore-scripts`.
  - --ignore-scripts: the explicit Build step already produced
    dist/; skip the prepack hook (which would re-invoke
    `pnpm run build`) to keep the publish path lean.
  - --no-git-checks: pnpm-specific flag; npm has no equivalent
    (publishes regardless of git state by default).
- Removed `env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}` —
  no token needed under OIDC.

CONTRIBUTING.md "One-time maintainer setup" step 1 rewritten:
- Was: "Secrets -> NPM_AUTH_TOKEN" (npm automation token).
- Now: "npm OIDC trusted publisher binding" — done by package
  maintainer on npmjs.com, points at this repo +
  npm-publish.yml. Failure mode if skipped: 401 Unauthorized
  referencing the missing trusted-publisher binding.

CHANGELOG.md Unreleased entry for the release-flow work updated
to reflect the OIDC switch (was previously written as if
NPM_AUTH_TOKEN was the auth path).

Gates: lint, typecheck, test (66/66), coverage 100/100/100/100,
build, docs:build all green. No src/ changes. Public exports
unchanged. Bundle 17 kB ESM (unchanged).
…-token bullet + sub-bullet docs

Three parallel review angles (security / senior-dev / docs+CTO):
all Approve-with-nits, zero blockers. Unanimous nit folded plus
two doc tightenings.

Unanimous (security + senior-dev):
- npm-publish.yml: `npm install -g npm@latest` -> `npm install -g
  'npm@^11.5.1'`. Closes a real supply-chain pivot — `@latest`
  would silently pull a hypothetical npm 12 major on the next
  release run with id-token:write in scope. Pinning to ^11.5.1
  means every bump above 11 will be a deliberate edit. Cheap to
  revisit once OIDC has been proven on one real release.
- Added `npm --version` after the install for a one-line debug
  aid in publish-run postmortems.

Security follow-up:
- CONTRIBUTING.md "One-time maintainer setup" step #1 gains an
  explicit "After the first successful OIDC publish, delete any
  stale NPM_AUTH_TOKEN secret" sub-bullet. Reasoning: keeping the
  secret "as fallback" defeats the threat-model win — a future
  workflow edit on main could re-add `env: NODE_AUTH_TOKEN:
  ${{ secrets.NPM_AUTH_TOKEN }}` and use it. Documented rollback:
  regenerate a fresh automation token if OIDC ever needs to be
  bypassed for a hotfix, rather than stockpiling one.

CTO doc tightening:
- CONTRIBUTING.md step #1 restructured: was one dense paragraph,
  now a 3-bullet sub-list (npm URL → Trusted Publishers tab →
  fields: Repository, Workflow filename, Environment). Friendlier
  for a maintainer who's never set up npm OIDC trusted publishing.

Skipped (out of scope for this PR):
- GitHub `environment: release` with required reviewers — admin-
  only setup; tag-vs-package.json check is the active defense for
  the workflow_dispatch path. Defense-in-depth follow-up.
- Smoke test via `--dry-run` or `--tag next` on a pre-release —
  documented as a maintainer choice, not gating.

Gates: lint, typecheck, test (66/66) all green. No src/ changes.
@IgorShevchik IgorShevchik merged commit e0a000a into main May 28, 2026
7 checks passed
@IgorShevchik IgorShevchik deleted the chore/npm-oidc branch May 29, 2026 04:24
IgorShevchik pushed a commit that referenced this pull request May 29, 2026
Manual reconciliation between release-please's auto-generated
[0.1.0] block (short subject lines from commits, plus 4 stale
uuidv7/type-msg leftovers from pre-0.0.4 history) and the
hand-written Unreleased block we maintained through PRs #10-#21
(rich per-entry descriptions with behavioural-change callouts).

This is the one-shot reconciliation flagged in CONTRIBUTING.md
"Release flow -> CHANGELOG reconciliation on the first auto-
release" — subsequent releases will land cleanly because the
hand-written approach ends with v0.1.

Kept from release-please:
- [0.1.0] heading with compare link and 2026-05-28 date.

Replaced with hand-written content:
- Added (3): release-please+OIDC #19/#21, TypeDoc #18,
  Logger DI #15.
- Changed (3): types any->unknown #16, consumer reconnect #14,
  producer prefetch #13.
- Removed (1): RabbitRPC dropped #12.
- Bug Fixes (2): ack/nack idempotency #17,
  base/registerQueue merge #10.

Dropped:
- 4 stale `fix(uuidv7): improve` entries from pre-0.0.4 history
  (already covered in [0.0.3] / [0.0.2] sections below).
- 1 stale `fix(type/Message): improve` (already in [0.0.4]).
- 7 noise `docs: improve` / `docs(en): fix nav` entries from
  pre-reanimation that release-please surfaced without
  bootstrap-sha.

Also captures PR #16 (chore(types)) + PR #19 / #21 (chore(release))
which release-please hides under the chore: hidden setting.
The hand-written entries describe what actually shipped.

Gates: lint, typecheck, test (66/66), build, docs:build all green.

Note: this commit is pushed to the release-please branch (not
main) — it amends release-please PR #20 in-place. The push from
a non-GITHUB_TOKEN identity also triggers PR-time CI, which the
original release-please-bot push could not because of GitHub's
loop-prevention rule.
IgorShevchik pushed a commit that referenced this pull request May 29, 2026
…e-push)

Same reconciliation as commit 86312d4 — release-please bot force-
pushed the release-please branch when PR #22 (ci aggregator)
merged to main, wiping the earlier reconciliation. This commit
restores it with the date refreshed to 2026-05-29.

Manual reconciliation between release-please's auto-generated
[0.1.0] block (short subject lines, plus stale uuidv7/type-msg
leftovers from pre-0.0.4 history) and the hand-written Unreleased
block we maintained through PRs #10-#21 (rich per-entry
descriptions with behavioural-change callouts).

The push from this identity (non-GITHUB_TOKEN) also triggers PR-
time CI on PR #20 — which now includes the `ci` aggregator job
that satisfies branch protection's required check (#22).

Kept from release-please: [0.1.0] heading + compare link + date.
Replaced body with hand-written rich content covering #10-#21,
dropped 4 stale uuidv7 entries (already in [0.0.3]/[0.0.2]),
1 stale type/Message entry (already in [0.0.4]), and 7 noisy
docs: improve entries from pre-reanimation.

Gates: lint, test (66/66), docs:build all green.
IgorShevchik pushed a commit that referenced this pull request May 29, 2026
First trustworthy release of @bitrix24/b24rabbitmq after the
full reanimation cycle. All three roadmap tracks closed:

- Track 1 (Correctness): every Phase 1 defect fixed test-first
  with characterisation locks (PRs #10, #12, #13, #14, #15,
  #16, #17).
- Track 2 (Onboarding & positioning): runnable examples,
  README integrator section, TypeDoc API reference + PR-time
  JSDoc gate (PRs #5, #18).
- Track 3 (Process & infrastructure): release-please-driven
  release flow, tag-triggered npm publish with OIDC trusted
  publishing + provenance, ci aggregator job for branch
  protection (PRs #19, #21, #22).

See the reconciled `[0.1.0]` block in CHANGELOG.md for the
rich per-PR descriptions and behavioural-change callouts.

Merging this PR creates the v0.1.0 git tag + GitHub Release,
which triggers .github/workflows/npm-publish.yml to publish
@bitrix24/b24rabbitmq@0.1.0 to npm via OIDC trusted
publishing with --provenance attestation.

Gates: lint, typecheck, unit tests (node 20 + 22), build,
docs:build, commit messages, ci aggregator — all green on
PR #20.
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