ci: publish to npm via OIDC trusted publishing#99
Conversation
Migrate the release pipeline off the long-lived NPM_TOKEN (the same kind of token that was just revoked on cipherstash/stack) to npm OIDC trusted publishing with provenance. - publish job: add `id-token: write`; drop NODE_AUTH_TOKEN and the Neon setup action (its setup-node `registry-url` writes a token .npmrc that shadows OIDC). Use a plain setup-node (no registry-url) on Node 22, upgrade npm to >= 11.5.1, and publish with `npm publish --access public --provenance`. - publish job runs on github-hosted ubuntu-latest: npm only accepts provenance from github-hosted runners. The Rust build matrix stays on Blacksmith. - setup job: drop the NPM_TOKEN presence check that failed the release when the secret was absent. - add `repository` (url + per-package directory) to the root package and all six platform packages — provenance validation requires repository.url to match the building repo. Requires a trusted publisher on npmjs.com for @cipherstash/protect-ffi and each of the six @cipherstash/protect-ffi-<platform> packages (org: cipherstash, repo: protectjs-ffi, workflow: release.yml).
📝 WalkthroughWalkthroughThis PR transitions npm publishing from NPM_TOKEN secret-based authentication to OIDC trusted publishing with provenance attestation in the release workflow, and adds repository metadata (type, URL, and directory paths) to the root and all six platform-specific package manifests. ChangesRelease workflow and package metadata updates
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested reviewers
Poem
🚥 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)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/workflows/release.yml:
- Around line 118-129: The workflow uses movable tag refs for third-party
actions (actions/checkout@v4, actions/setup-node@v4, and
actions/create-github-app-token@v1); replace each tag ref with the corresponding
immutable commit SHA (e.g., actions/checkout@<commit-sha>) by looking up the
recommended commit SHA for each action on its GitHub repo or marketplace and
updating the uses entries, then verify the workflow parses and runs with the
pinned SHAs.
- Around line 113-120: Update the publish job permissions to use
permissions.contents: read instead of write, and modify the actions/checkout@v4
step to include with: persist-credentials: false so checkout credentials are not
carried into later steps; locate the permissions block containing
permissions.contents and the Checkout Code step using actions/checkout@v4 and
make those two changes (reduce contents permission and add persist-credentials:
false).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8cb3ff23-200b-4547-a93a-312572bddb9b
📒 Files selected for processing (8)
.github/workflows/release.ymlpackage.jsonplatforms/darwin-arm64/package.jsonplatforms/darwin-x64/package.jsonplatforms/linux-arm64-gnu/package.jsonplatforms/linux-x64-gnu/package.jsonplatforms/linux-x64-musl/package.jsonplatforms/win32-x64-msvc/package.json
| permissions: | ||
| contents: write | ||
| id-token: write # Required for npm OIDC trusted publishing | ||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v3 | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ needs.setup.outputs.ref }} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the publish job's permissions and checkout credential handling.
sed -n '104,130p' .github/workflows/release.yml
rg -n -C2 'contents:|persist-credentials' .github/workflows/release.ymlRepository: cipherstash/protectjs-ffi
Length of output: 1669
Harden publish permissions and disable checkout credential persistence
The publish job in .github/workflows/release.yml grants permissions.contents: write, but it only needs to read the repository/release assets. The actions/checkout@v4 step does not set persist-credentials: false, so checkout credentials aren’t explicitly prevented from being carried into later steps. Reduce contents to read and disable credential persistence on checkout.
Suggested hardening
publish:
name: Publish
@@
permissions:
- contents: write
+ contents: read
id-token: write # Required for npm OIDC trusted publishing
@@
- name: Checkout Code
uses: actions/checkout@v4
with:
ref: ${{ needs.setup.outputs.ref }}
+ 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.
| permissions: | |
| contents: write | |
| id-token: write # Required for npm OIDC trusted publishing | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v3 | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.setup.outputs.ref }} | |
| permissions: | |
| contents: read | |
| id-token: write # Required for npm OIDC trusted publishing | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.setup.outputs.ref }} | |
| persist-credentials: false |
🧰 Tools
🪛 zizmor (1.25.2)
[error] 118-118: 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/release.yml around lines 113 - 120, Update the publish job
permissions to use permissions.contents: read instead of write, and modify the
actions/checkout@v4 step to include with: persist-credentials: false so checkout
credentials are not carried into later steps; locate the permissions block
containing permissions.contents and the Checkout Code step using
actions/checkout@v4 and make those two changes (reduce contents permission and
add persist-credentials: false).
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ needs.setup.outputs.ref }} | ||
| - name: Setup Neon Environment | ||
| uses: ./.github/actions/setup | ||
| # OIDC trusted publishing needs Node >= 22.14 and npm >= 11.5.1, and must | ||
| # NOT have a token .npmrc. Use setup-node WITHOUT registry-url (the Neon | ||
| # setup action sets registry-url, which writes a //registry/:_authToken | ||
| # line that shadows OIDC) — this job only publishes prebuilt tarballs, so | ||
| # it needs no Rust toolchain or npm install. | ||
| - name: Install Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| use-rust: false | ||
| node-version: 22 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify which action references in the release workflow are still tag-based.
rg -n 'uses:\s+[^@]+@v[0-9]+' .github/workflows/release.ymlRepository: cipherstash/protectjs-ffi
Length of output: 236
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/release.yml"
echo "=== uses: lines in release.yml ==="
rg -n '^\s*-\s+uses:|^\s*uses:' "$FILE" -S || true
echo
echo "=== uses@ references (extract owner/name@ref) ==="
rg -n 'uses:\s*([^@]+)@([^\s]+)' "$FILE" -S | sed -E 's/^[0-9]+:.*uses:\s*([^@]+)@([^ ]+).*/\1@\2/'
echo
echo "=== Check for pinned SHAs vs tags (40-hex) ==="
python3 - <<'PY'
import re, pathlib
p=pathlib.Path(".github/workflows/release.yml")
txt=p.read_text()
pat=re.compile(r'uses:\s*([^@]+)@([^\s]+)')
sha=re.compile(r'^[0-9a-fA-F]{40}$')
for m in pat.finditer(txt):
ref=m.group(2)
status="PINNED_SHA" if sha.match(ref) else "TAG_OR_OTHER"
print(f"{m.group(1)}@{ref} -> {status}")
PY
echo
echo "=== Context around lines 110-140 ==="
nl -ba "$FILE" | sed -n '100,150p'Repository: cipherstash/protectjs-ffi
Length of output: 2538
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/release.yml"
echo "=== lines 110-135 ==="
awk 'NR>=110 && NR<=135 {printf "%d:%s\n", NR, $0}' "$FILE"
echo
echo "=== uses: tag-based (not 40-hex SHA) ==="
python3 - <<'PY'
import re, pathlib
txt=pathlib.Path(".github/workflows/release.yml").read_text()
pat=re.compile(r'uses:\s*([^@]+)@([^\s]+)')
sha=re.compile(r'^[0-9a-fA-F]{40}$')
out=[]
for m in pat.finditer(txt):
ref=m.group(2)
if not sha.match(ref):
out.append((m.group(1), ref))
for owner, ref in out:
print(f"{owner}@{ref}")
PYRepository: cipherstash/protectjs-ffi
Length of output: 2937
Pin tag-based GitHub Actions to immutable SHAs in the publish workflow.
- Line 118:
actions/checkout@v4is a movable tag - Line 127:
actions/setup-node@v4is a movable tag
Also present:actions/create-github-app-token@v1is tag-based.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 118-118: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 127-127: 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/release.yml around lines 118 - 129, The workflow uses
movable tag refs for third-party actions (actions/checkout@v4,
actions/setup-node@v4, and actions/create-github-app-token@v1); replace each tag
ref with the corresponding immutable commit SHA (e.g.,
actions/checkout@<commit-sha>) by looking up the recommended commit SHA for each
action on its GitHub repo or marketplace and updating the uses entries, then
verify the workflow parses and runs with the pinned SHAs.
Why
@cipherstash/protect-ffiand its six@cipherstash/protect-ffi-<platform>binary packages publish from this repo using a long-livedNPM_TOKEN— the same class of token npm just revoked oncipherstash/stack, which broke that repo's releases. This migrates to npm OIDC trusted publishing (short-lived, no stored secret) with provenance.Changes
release.yml—publishjobid-token: write.NODE_AUTH_TOKENand the Neon setup action. The Neon setup runssetup-nodewithregistry-url, which writes a//registry.npmjs.org/:_authToken=…line that shadows OIDC. The publish job only ships prebuilt tarballs, so it needs no Rust toolchain ornpm ci— replaced with a plainsetup-node(Node 22, noregistry-url) +npm install -g npm@^11.5.1(OIDC needs npm ≥ 11.5.1 / Node ≥ 22.14).npm publish --access public --provenance.ubuntu-latest(github-hosted): npm only accepts provenance from github-hosted runners (self-hosted → E422). The Rust build matrix stays on Blacksmith.release.yml—setupjobNPM_TOKENpresence check (it would fail the release once the secret is removed).repositoryfieldsrepository(url + per-packagedirectory) to the root package and all sixplatforms/*/package.json. Provenance validation requiresrepository.urlto match this repo.Required on npmjs.com before this can publish
Configure a Trusted Publisher (org
cipherstash, repoprotectjs-ffi, workflowrelease.yml) for all seven packages:@cipherstash/protect-ffi,@cipherstash/protect-ffi-darwin-x64,-darwin-arm64,-win32-x64-msvc,-linux-x64-gnu,-linux-arm64-gnu,-linux-x64-musl.Summary by CodeRabbit