feat(monorepo-release): opt-in npm OIDC Trusted Publishing#79
feat(monorepo-release): opt-in npm OIDC Trusted Publishing#79Yan Xue (yanxue06) wants to merge 1 commit into
Conversation
Mirrors the use-oidc pattern from typescript-service-release (#77) to the monorepo workflow so each monorepo package can publish tokenlessly with provenance when a trusted publisher is configured. - typescript-monorepo-release: add `use-oidc` input (default false) and split `npm-publish` into two jobs. The default path keeps the existing `contents: read` least-privilege publish via NPM_TOKEN. The opt-in `npm-publish-oidc` job omits an explicit permissions block so the caller's `id-token: write` can flow through; it never *requests* id-token, so callers that haven't granted it can't be hard-failed - the publish step falls back to NPM_TOKEN. NPM_TOKEN secret is now optional. - publish-npm-packages: per-package OIDC attempt first. bun publish doesn't speak OIDC yet (oven-sh/bun#15601), so the OIDC path packs with `bun pm pack` (preserving the `workspace:*` rewriting that the switch to bun publish in db9d3f3 fixed) and publishes the tarball with `npm publish --provenance`. The token fallback continues to use `bun publish` exactly as before. `npm-token` input is now optional; the action only errors when both OIDC is unavailable and no token is provided. Dry-runs still work without any credentials. - README: monorepo workflow gets the `use-oidc` row + an activation blurb mirroring the service workflow's, plus a Publishing modes subsection on the publish-npm-packages block. Activating OIDC for a monorepo still requires the caller to grant `id-token: write`, a trusted publisher configured on npmjs.com *per package*, and npm >= 11.5.1 on the runner. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
📝 WalkthroughWalkthroughThis PR introduces npm OIDC Trusted Publishing as an opt-in feature for the TypeScript monorepo release workflow and its composite publish action. The composite action now handles dry-run upfront, attempts tokenless OIDC-based publishing first, and falls back to token-based publishing if OIDC is unavailable or fails. The workflow adds a new input to enable OIDC mode and routes publishing through separate jobs accordingly. Changesnpm OIDC Trusted Publishing with token fallback
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
📚 Skills documentation may need an updateThis PR introduces changes that might not be reflected in the skills documentation. Reason: The PR adds opt-in npm OIDC Trusted Publishing for
|
📄 README may need an updateThis PR introduces changes that might not be reflected in Reason:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/blocks/publish-npm-packages/action.yaml:
- Around line 94-117: The OIDC publish path requires Node >=22.14.0 and npm CLI
>=11.5.1 but the action defaults inputs.node-version to 20, causing OIDC
publishes to fail; update the action default for inputs.node-version to at least
22.14.0 (or a newer LTS like 22.x/24.x) and ensure the workflow's setup steps
use that input before the OIDC publish block, and either add a step to upgrade
npm to >=11.5.1 (e.g., npm install -g npm@11.5.1+) or document/validate the npm
version before running the OIDC publish; specifically change the default
inputs.node-version and verify the OIDC publish section (the code around
PUBLISHED and the npm publish --provenance invocation) runs under that Node/npm
version.
In @.github/workflows/typescript-monorepo-release.yaml:
- Around line 73-75: Add a preflight validation step before any version
bump/release steps that checks the workflow inputs: if inputs.dry-run is "false"
AND inputs.use-oidc is "false" AND the NPM_TOKEN secret is empty, fail the run
immediately; implement this as a dedicated job/step (e.g., id
"validate-publish-params" or a top-of-job step before
"bump-version"/"github-release"/"npm-publish") that uses an if-condition and a
small shell script to echo a clear error and exit 1 when the three conditions
are met so the workflow fails fast instead of proceeding to version bumping or
parallel release steps.
- Around line 220-223: The checkout step using actions/checkout@v5 in the
npm-publish-oidc job leaves authenticated credentials persisted; update that
checkout invocation to set persist-credentials: false so credentials are not
kept for later steps. Locate the actions/checkout@v5 step (the checkout step in
the npm-publish-oidc job) and add the persist-credentials: false key alongside
ref and fetch-depth.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4b00f0e4-7d2b-432f-9457-ff6e7a9d519b
📒 Files selected for processing (3)
.github/blocks/publish-npm-packages/action.yaml.github/workflows/typescript-monorepo-release.yamlREADME.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Agent
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-04-05T00:07:12.194Z
Learnt from: qwerzl
Repo: photon-hq/buildspace PR: 62
File: .github/workflows/update-docs.yaml:164-167
Timestamp: 2026-04-05T00:07:12.194Z
Learning: In this repo (photon-hq/buildspace), reusable workflow blocks referenced under photon-hq/buildspace (e.g., `photon-hq/buildspace/.github/blocks/<block>main`) are intentionally pinned to `main` because the blocks and workflows ship together and are kept in sync. During review, do not flag these references as version-pinning/supply-chain issues just because they use `main`; only require pinning to a release tag if there’s a separate reason (e.g., referencing an external repo or a non-controlled branch).
Applied to files:
.github/workflows/typescript-monorepo-release.yaml
📚 Learning: 2026-04-27T01:30:22.893Z
Learnt from: yanxue06
Repo: photon-hq/buildspace PR: 73
File: .github/workflows/check-readme.yaml:18-18
Timestamp: 2026-04-27T01:30:22.893Z
Learning: When reviewing this repo’s GitHub Actions workflows, treat Blacksmith runner labels like `blacksmith-4vcpu-ubuntu-2404` and other `blacksmith-*vcpu-ubuntu-*` values as valid/intentional third-party runner labels (Blacksmith: blacksmith.sh). Do not flag them as unknown or non-standard runner labels—these are an intentional drop-in replacement for GitHub-hosted runners.
Applied to files:
.github/workflows/typescript-monorepo-release.yaml
📚 Learning: 2026-04-27T01:30:22.893Z
Learnt from: yanxue06
Repo: photon-hq/buildspace PR: 73
File: .github/workflows/check-readme.yaml:18-18
Timestamp: 2026-04-27T01:30:22.893Z
Learning: In photon-hq/buildspace GitHub workflow YAML files, runner labels that match `blacksmith-*vcpu-ubuntu-*` (e.g., `blacksmith-4vcpu-ubuntu-2404`) are intentionally managed by the Blacksmith CI service (blacksmith.sh) and serve as drop-in replacements for GitHub-hosted ephemeral microVM runners. Do not flag these labels as unknown/non-standard, supply-chain concerns, or “unrecognized runner” issues; any linter/actionlint warnings about these specific labels are expected false positives.
Applied to files:
.github/workflows/typescript-monorepo-release.yaml
🪛 zizmor (1.25.2)
.github/workflows/typescript-monorepo-release.yaml
[warning] 220-223: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 220-220: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 226-226: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
| # Step 1: try npm OIDC Trusted Publishing (tokenless, with | ||
| # provenance). bun publish doesn't yet support OIDC | ||
| # (oven-sh/bun#15601), so we use the documented workaround: pack | ||
| # with bun (preserves workspace:* rewriting that npm pack would | ||
| # leave broken) and publish the tarball with npm. The runner only | ||
| # sets ACTIONS_ID_TOKEN_REQUEST_TOKEN when the job has | ||
| # 'id-token: write', so when that permission is absent we skip OIDC | ||
| # entirely rather than run a publish that is guaranteed to fail on | ||
| # auth. | ||
| PUBLISHED=false | ||
| if [ -n "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ]; then | ||
| echo "🔒 Attempting publish of $NAME via npm Trusted Publishing (OIDC)..." | ||
| if ( | ||
| cd "$PKG_PATH" | ||
| rm -f -- *.tgz 2>/dev/null || true | ||
| bun pm pack | ||
| TARBALL=$(ls -t -- *.tgz | head -n 1) | ||
| [ -n "$TARBALL" ] | ||
| npm publish "$TARBALL" --tag "$TAG" --access public --provenance | ||
| ); then | ||
| echo "✅ Published $NAME via OIDC Trusted Publishing." | ||
| PUBLISHED=true | ||
| else | ||
| echo "⚠️ OIDC publish of $NAME failed. Falling back to NPM token." |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/blocks/publish-npm-packages/action.yaml"
if [ ! -f "$FILE" ]; then
echo "Missing file: $FILE" >&2
exit 1
fi
echo "== Relevant inputs defaults =="
sed -n '1,180p' "$FILE" | nl -ba | sed -n '1,180p'
echo
echo "== Locate node-version default explicitly =="
rg -n "node-version" "$FILE" || true
echo
echo "== Locate OIDC/publish logic =="
rg -n "Trusted Publishing|ACTIONS_ID_TOKEN_REQUEST_TOKEN|--provenance|npm publish" "$FILE" || trueRepository: photon-hq/buildspace
Length of output: 157
🌐 Web query:
npm trusted publishing minimum node version 22.14.0 npm 11.5.1
💡 Result:
To utilize npm trusted publishing, you must use npm CLI version 11.5.1 or later and Node.js version 22.14.0 or higher [1][2][3][4]. These minimum version requirements ensure compatibility with the OIDC-based authentication mechanism used to establish trust relationships between your package and CI/CD providers [1][3]. If your environment is running an older version of Node.js or npm, you can satisfy the requirement by manually updating npm within your CI/CD workflow before performing the publish operation, for example by running: npm install -g npm@latest [2][4][5] Alternatively, you can ensure your CI/CD runner is using an environment that includes these versions by default (e.g., Node.js 24+) [6][5].
Citations:
- 1: https://docs.npmjs.com/trusted-publishers/
- 2: https://philna.sh/blog/2026/01/28/trusted-publishing-npm/
- 3: https://www.mbgsec.com/_archive/2025-09-23-trusted-publishing-for-npm-packages-npm-docs/
- 4: https://dev.to/philnash/things-you-need-to-do-for-npm-trusted-publishing-to-work-46i2
- 5: https://projen.io/docs/publishing/trusted-publishing/
- 6: https://dev.to/luzaramburo/supply-chain-attacks-arent-just-a-big-library-problem-heres-what-you-can-do-today-1jbg
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/blocks/publish-npm-packages/action.yaml"
test -f "$FILE" || { echo "Missing file: $FILE" >&2; exit 1; }
echo "== File head =="
sed -n '1,120p' "$FILE"
echo
echo "== Inputs section (best-effort) =="
rg -n "inputs:|node-version|npm-token|provenance|dry-run" "$FILE" || true
echo
echo "== Around node-version definition =="
# print a small window around the first node-version match
LINE="$(rg -n "node-version" "$FILE" | head -n 1 | cut -d: -f1)"
START=$((LINE-20)); END=$((LINE+20))
sed -n "${START},${END}p" "$FILE"
echo
echo "== Around OIDC publish branch =="
rg -n "Trusted Publishing|ACTIONS_ID_TOKEN_REQUEST_TOKEN|bun pm pack|npm publish.*--provenance" "$FILE" || true
OIDC_LINE="$(rg -n "ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$FILE" | head -n 1 | cut -d: -f1)"
START=$((OIDC_LINE-25)); END=$((OIDC_LINE+70))
sed -n "${START},${END}p" "$FILE"Repository: photon-hq/buildspace
Length of output: 7272
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/blocks/publish-npm-packages/action.yaml"
test -f "$FILE"
echo "== OIDC block + fallback (lines 80-180) =="
awk 'NR>=80 && NR<=180 {printf "%5d:%s\n", NR, $0}' "$FILE"Repository: photon-hq/buildspace
Length of output: 3145
🌐 Web query:
Node.js 24 bundled npm version (npm CLI version) trusted publishing npm 11.5.1
💡 Result:
Node.js 24 was released on May 6, 2025 [1][2], and shipped with npm 11.0.0 [3][2]. Node.js versions are bundled with specific minor versions of npm that evolve over the lifecycle of the release line through subsequent updates [4]. Regarding trusted publishing: - Trusted publishing is a security feature that allows developers to publish packages from CI/CD environments using OpenID Connect (OIDC) rather than long-lived authentication tokens [5]. - The official npm documentation explicitly states that trusted publishing requires npm CLI version 11.5.1 or later [5][6]. - Because Node.js 24 receives regular updates, the bundled npm version has been incremented beyond 11.5.1 in later releases [4]. For example, Node.js 24.14.0 included npm 11.9.0 [4], and Node.js 24.15.0 included npm 11.12.1 [4]. Therefore, users running a recent version of Node.js 24 will satisfy the npm version requirement for trusted publishing by default [5][7]. If you are using an earlier release of Node.js 24 that includes an npm version older than 11.5.1, you can update npm globally or within your CI/CD environment using the command: npm install -g npm@latest [8][7]. Users can also use the npm trust command, which was introduced to manage these relationships directly from the CLI [8][9].
Citations:
- 1: Release plan - v24.x Active LTS nodejs/Release#1089
- 2: https://blog.logrocket.com/node-js-24-new/
- 3: https://www.redhat.com/en/blog/introduction-nodejs-24-from-red-hat
- 4: https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V24.md
- 5: https://docs.npmjs.com/trusted-publishers/
- 6: https://philna.sh/blog/2026/01/28/trusted-publishing-npm/
- 7: https://projen.io/docs/publishing/trusted-publishing/
- 8: https://docs.npmjs.com/cli/v11/commands/npm-trust/
- 9: feat: npm trust command npm/cli#8899
Fix OIDC publish default Node runtime to meet npm trusted publishing requirements
The OIDC path runs npm publish ... --provenance, but the action defaults inputs.node-version to 20. npm trusted publishing requires Node >= 22.14.0 and npm CLI >= 11.5.1, so callers enabling OIDC without overriding node-version can hit the “OIDC failed → fallback” path (or hard-fail if npm-token wasn’t provided).
Suggested fix
node-version:
description: '[string] Node.js version'
required: false
- default: '20'
+ default: '24'🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/blocks/publish-npm-packages/action.yaml around lines 94 - 117, The
OIDC publish path requires Node >=22.14.0 and npm CLI >=11.5.1 but the action
defaults inputs.node-version to 20, causing OIDC publishes to fail; update the
action default for inputs.node-version to at least 22.14.0 (or a newer LTS like
22.x/24.x) and ensure the workflow's setup steps use that input before the OIDC
publish block, and either add a step to upgrade npm to >=11.5.1 (e.g., npm
install -g npm@11.5.1+) or document/validate the npm version before running the
OIDC publish; specifically change the default inputs.node-version and verify the
OIDC publish section (the code around PUBLISHED and the npm publish --provenance
invocation) runs under that Node/npm version.
| NPM_TOKEN: | ||
| required: true | ||
| description: "NPM token for publishing" | ||
| required: false | ||
| description: "NPM token (required for npm publish when not using OIDC Trusted Publishing, or as a fallback when OIDC isn't configured for some packages)" |
There was a problem hiding this comment.
Restore an early guard for the non-OIDC publish path.
Making NPM_TOKEN optional here removes the only fail-fast check for the default use-oidc: false path. If a caller forgets the secret, this workflow now gets through version bumping, and github-release can run in parallel with npm-publish, so you can create a GitHub release before publish finally dies inside the block. Please add a preflight validation that fails before bumping/releasing when dry-run is false, use-oidc is false, and NPM_TOKEN is empty.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/typescript-monorepo-release.yaml around lines 73 - 75, Add
a preflight validation step before any version bump/release steps that checks
the workflow inputs: if inputs.dry-run is "false" AND inputs.use-oidc is "false"
AND the NPM_TOKEN secret is empty, fail the run immediately; implement this as a
dedicated job/step (e.g., id "validate-publish-params" or a top-of-job step
before "bump-version"/"github-release"/"npm-publish") that uses an if-condition
and a small shell script to echo a clear error and exit 1 when the three
conditions are met so the workflow fails fast instead of proceeding to version
bumping or parallel release steps.
| - uses: actions/checkout@v5 | ||
| with: | ||
| ref: ${{ github.ref_name }} | ||
| fetch-depth: 0 |
There was a problem hiding this comment.
Disable checkout credential persistence in the OIDC publish job (npm-publish-oidc).
The actions/checkout@v5 step at ref/fetch-depth omits persist-credentials: false; actions/checkout defaults it to true, leaving authenticated credentials available to later steps in the same job. Set persist-credentials: false on this checkout step.
Suggested fix
- uses: actions/checkout@v5
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
+ persist-credentials: false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.ref_name }} | |
| fetch-depth: 0 | |
| - uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.ref_name }} | |
| fetch-depth: 0 | |
| persist-credentials: false |
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 220-223: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 220-220: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/typescript-monorepo-release.yaml around lines 220 - 223,
The checkout step using actions/checkout@v5 in the npm-publish-oidc job leaves
authenticated credentials persisted; update that checkout invocation to set
persist-credentials: false so credentials are not kept for later steps. Locate
the actions/checkout@v5 step (the checkout step in the npm-publish-oidc job) and
add the persist-credentials: false key alongside ref and fetch-depth.
Summary
Mirrors the
use-oidcopt-in pattern fromtypescript-service-release(#77) to the monorepo workflow and underlyingpublish-npm-packagesblock, so each monorepo package can publish tokenlessly with provenance when a trusted publisher is configured.typescript-monorepo-release.yaml— addsuse-oidcinput (defaultfalse). The defaultnpm-publishjob keeps the existingcontents: readleast-privilege publish viaNPM_TOKEN. A newnpm-publish-oidcjob (opt-in) omits an explicitpermissions:block so the caller'sid-token: writecan flow through. Because that job never requestsid-token, callers that haven't granted it can't be hard-failed — the publish step falls back toNPM_TOKEN.NPM_TOKENsecret is nowrequired: false.publish-npm-packages/action.yaml— per-package OIDC attempt first.bun publishdoesn't speak OIDC yet (oven-sh/bun#15601), so the OIDC path packs each package withbun pm pack(preserving theworkspace:*rewriting thatdb9d3f3fixed) and publishes the tarball withnpm publish --provenance. The token fallback continues to usebun publishexactly as before.npm-tokeninput is now optional; the action only errors when both OIDC is unavailable and no token is provided. Dry-runs still work without any credentials.README.md— adds theuse-oidcrow + activation blurb to the monorepo section (mirroring the service workflow's), plus a Publishing modes subsection on thepublish-npm-packagesblock docs.Safety properties
use-oidcare unaffected: samecontents: read, samebun publish+NPM_TOKENflow.id-token: writearen't hard-failed: the runner won't setACTIONS_ID_TOKEN_REQUEST_TOKEN, so the action skips the OIDC attempt entirely and falls back toNPM_TOKEN.Activating OIDC for a monorepo still requires the caller to grant
id-token: write, a trusted publisher configured on npmjs.com per package, and npm ≥ 11.5.1 on the runner.Test plan
Build and publish packagesstep parses cleanly (bash -n)bun/npm:bun publishbun publish(current behavior)use-oidc: false(verify zero behavior change vs. main)use-oidc: true+id-token: write+ a configured trusted publisher (verify tokenless publish with provenance)use-oidc: truebut skipping the trusted-publisher config on one package — that package should fall back toNPM_TOKENwhile OIDC packages succeedMade with Cursor
Need help on this PR? Tag
@codesmithwith what you need. Autofix is disabled.Summary by CodeRabbit
Release Notes
New Features
Documentation