Skip to content

ci(after-merge): Enable npm OIDC trusted publishing on dev-canary#12683

Open
usmanmani1122 wants to merge 1 commit into
masterfrom
usman/pak-418-use-oidc-trusted-publishing-for-npm-package-release
Open

ci(after-merge): Enable npm OIDC trusted publishing on dev-canary#12683
usmanmani1122 wants to merge 1 commit into
masterfrom
usman/pak-418-use-oidc-trusted-publishing-for-npm-package-release

Conversation

@usmanmani1122
Copy link
Copy Markdown
Contributor

refs: #XXXX

Description

Enables npm OIDC trusted publishing on the dev-canary job in .github/workflows/after-merge.yml, and fixes one prerequisite in package metadata. This is the second of two PRs:

Changes

1. Workflow (.github/workflows/after-merge.yml) — adds a job-level permissions block to dev-canary:

permissions:
  contents: read    # for actions/checkout
  id-token: write   # for npm OIDC token exchange

Once id-token: write is granted, @lerna-lite/publish (≥4.9.0) automatically:

  1. Detects GitHub Actions via ACTIONS_ID_TOKEN_REQUEST_URL/ACTIONS_ID_TOKEN_REQUEST_TOKEN env vars.
  2. Fetches a GitHub-issued OIDC token.
  3. POSTs to https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/${pkg}.
  4. Uses the returned short-lived publish token for that package only.

The existing configure NPM token and check credentials steps are intentionally kept as transitional fallback (see "Why we keep NPM_TOKEN" below).

2. repository.url fixes (12 packages) — npm trusted publishing uses the package's repository field to issue provenance attestations and to cross-check the GitHub origin. Audit findings:

  • 11 packages had no repository field at all: @agoric/client-utils, @agoric/eslint-plugin, @agoric/fast-usdc, @agoric/internal, @agoric/pola-io, @agoric/portfolio-api, @agoric/swingset-xsnap-supervisor, @agoric/vow, @agoric/wallet, @agoric/xsnap, @agoric/xsnap-lockdown.
  • 1 package pointed at a stale GitHub URL: @agoric/smart-walletgit+https://github.com/Agoric/agoric (now corrected to Agoric/agoric-sdk).

All 12 are now set to the canonical object form used by the majority of public packages:

"repository": {
  "type": "git",
  "url": "git+https://github.com/Agoric/agoric-sdk.git"
}

Inserted just after description (or version if no description) — the position used by the most idiomatic existing packages. Existing packages that already use the string shorthand ("repository": "https://github.com/Agoric/agoric-sdk") are not touched; npm normalizes both forms to the same canonical URL.

After this change all 48 publicly-published packages have a repository.url matching github.com/Agoric/agoric-sdk.

Prerequisite (out of band, not part of this PR)

For OIDC to take effect for a given package, a Trusted Publisher must be configured on npmjs.com:

  • package → Settings → Trusted publishing → Add trusted publisher
  • Owner: Agoric
  • Repository: agoric-sdk
  • Workflow filename: after-merge.yml
  • Environment: (none)
  • Allowed actions: npm publish

Until that config exists for a package, OIDC silently falls back to NPM_TOKEN. Configuration is per-package — 48 publicly-published packages under packages/* (every package.json without "private": true).

Why we keep NPM_TOKEN as a fallback

Verified in @lerna-lite/publish@4.9.4 source (packages/publish/src/lib/oidc.ts:134-147): the per-package /oidc/token/exchange call is wrapped in a try/catch that returns undefined on any non-2xx response without mutating opts/config. The caller in lib/npm-publish.ts:56-62 does not check the return value, so the subsequent libnpmpublish.publish() call uses whatever _authToken is already on the registry config — i.e., the ~/.npmrc line written by our configure NPM token step.

Failure path → silent fallback:

  • Per-package, every package. A 48-package run = up to 48 independent exchange attempts.
  • Each failure logs only verbose oidc Failed token exchange request ... (visible at --loglevel verbose).
  • Library docstring (oidc.ts:31-32): "This function is intended to never throw... OIDC is always an optional feature, and the function should not throw if OIDC is not configured by the registry."

This means we can:

  1. Merge this PR before configuring all packages on npmjs.com.
  2. Onboard packages incrementally — each one starts using OIDC the moment its trusted publisher is configured.
  3. Remove NPM_TOKEN in a follow-up PR once we've verified the fallback isn't triggered for any package (i.e., the granular token is no longer needed).

Files changed

  • .github/workflows/after-merge.yml — adds permissions: block on dev-canary job; two short inline comments explaining the OIDC enablement and the transitional fallback.
  • packages/{client-utils,eslint-plugin,fast-usdc,internal,pola-io,portfolio-api,swingset-xsnap-supervisor,vow,wallet,xsnap,xsnap-lockdown}/package.json — add canonical repository field.
  • packages/smart-wallet/package.json — replace stale Agoric/agoric repository.url with the correct Agoric/agoric-sdk URL, normalize placement.

Security Considerations

This PR reduces the long-term attack surface:

  • Today: a long-lived granular access token belonging to agoricbot is stored as NPM_TOKEN secret. Compromise of the secret = ability to publish any of the 48 packages until manually revoked.
  • After OIDC onboarding completes: no long-lived publish credential. Each publish exchanges a short-lived (≤10 min) OIDC token bound to a specific (repo, workflow, package) tuple. Compromise of the workflow file or the runner is still a risk, but the credential cannot be exfiltrated for later use.

Transitional state (this PR + NPM_TOKEN coexisting): identical risk profile to today, because the granular token is still present. OIDC adds an additional, per-package, short-lived credential path that is preferred when available.

The permissions: block is scoped to the dev-canary job, not workflow-wide — id-token: write is not granted to the build, coverage, or benchmark jobs. Once permissions: is declared, all unlisted permissions become none for that job (least-privilege default), so contents: read is explicit for actions/checkout.

The repository.url fix on @agoric/smart-wallet corrects a long-standing stale reference — no security implication beyond ensuring provenance attestations point at the right repo.

Scaling Considerations

None. Adds one HTTPS round-trip per published package to registry.npmjs.org/-/npm/v1/oidc/token/exchange/.... The publish concurrency is already 1 (--concurrency 1, gated on agoric-sdk#8091), so the extra latency is roughly 48 × ~100ms = a few seconds per release.

Documentation Considerations

The permissions: block carries an inline comment pointing at the fallback behavior. No external documentation change is needed yet — once NPM_TOKEN is fully removed, the rollback playbook in any internal release docs (if it references the granular token) should be updated.

Testing Considerations

This workflow runs only on push to master, release-*, or dev-*. CI verification options:

  • Dry run is not available — @lerna-lite/publish 4.9.x verifies OIDC against the live registry only at publish time. (4.10.0+ adds dry-run OIDC verification, but we're intentionally on 4.9.4.)
  • Live verification on merge: after merge to master, watch the dev-canary job logs. With --loglevel verbose on lerna publish (consider a follow-up if signal is needed), look for:
    • verbose oidc Successfully retrieved oidc token from package ... → OIDC working for that package.
    • verbose oidc Failed token exchange request ... → fallback to NPM_TOKEN; trusted publisher not yet configured.
  • Belt-and-suspenders: NPM_TOKEN is still wired, so even a total OIDC failure cannot break the release while the fallback exists.

Upgrade Considerations

  • npmjs.com Trusted Publisher configuration is required per package for OIDC to take effect; this is a manual one-time step per package (no API-level batch operation). Tracking outside this PR.
  • The ~/.npmrc _authToken written by configure NPM token is global to the runner for the duration of the job; if any other step in the job spawns npm publish independently, it would also use NPM_TOKEN, not OIDC. Currently nothing else publishes from this job.
  • After all packages are onboarded, the cleanup PR should: remove the configure NPM token and check credentials steps, drop the NPM_TOKEN GitHub Actions secret, and delete the agoricbot granular access token on npmjs.com.

@usmanmani1122 usmanmani1122 self-assigned this May 22, 2026
Copy link
Copy Markdown
Member

@turadg turadg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(4.10.0+ adds dry-run OIDC verification, but we're intentionally on 4.9.4.)

Updating the patch for 4.10 seems worth getting the dry run but I'll leave the operational risk trade-offs to you.

"name": "@agoric/client-utils",
"version": "0.1.0",
"description": "Utilities for building Agoric clients",
"repository": {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these package.json changes should have been a chore commit separate from the ci one.

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