ci(release): split release into read-only gate + minimal publish job - #7
Conversation
…(least-privilege OIDC)
📝 WalkthroughWalkthroughThe release workflow is restructured into two jobs: a read-only ChangesRelease Workflow Restructuring
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Trigger as Workflow trigger
participant Gate as gate job
participant Publish as publish job
participant NpmRegistry as npm registry
participant GitHub as GitHub Release
Trigger->>Gate: checkout, npm ci, npm run ci
Gate->>Gate: pack:check (prepack)
Gate->>Gate: build with telemetry env vars
Gate->>Gate: upload bundle/ artifact
Gate-->>Publish: outputs.publish (real/dry-run)
Publish->>Publish: download bundle/ artifact
Publish->>Publish: configure OIDC/Trusted Publishing
alt outputs.publish == real and version not on npm
Publish->>NpmRegistry: npm publish --provenance --ignore-scripts
Publish->>GitHub: create Release
else dry-run or already published
Publish->>Publish: skip real publish/release
end
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.yaml:
- Around line 257-268: The release workflow’s OIDC auth cleanup is happening too
late, so the scaffolded `_authToken` from setup-node can still affect the first
npm command. Move the “Enable OIDC trusted publishing (strip scaffolded auth
token)” step to run before the “Upgrade npm” step in the release workflow,
keeping the auth-strip logic in place and leaving any --ignore-scripts hardening
optional.
🪄 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 Plus
Run ID: 06389f2a-95bd-4678-bd1c-2c76429120cc
📒 Files selected for processing (1)
.github/workflows/release.yaml
| - name: Upgrade npm (Trusted Publishing needs npm >= 11.5.1) | ||
| run: npm install -g npm@11.6.2 | ||
|
|
||
| # Strip setup-node's dummy `_authToken` line so OIDC authenticates the | ||
| # publish (npm uses OIDC only when NO credential is configured). The | ||
| # registry line is KEPT. Diagnostics printed (token redacted) for a future | ||
| # auth failure. | ||
| - name: Enable OIDC trusted publishing (strip scaffolded auth token) | ||
| run: | | ||
| npmrc="${NPM_CONFIG_USERCONFIG:-$HOME/.npmrc}" | ||
| if [ -f "$npmrc" ]; then | ||
| sed -i '/:_authToken=/d' "$npmrc" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the workflow context around the flagged lines.
nl -ba .github/workflows/release.yaml | sed -n '220,310p'
# Find setup-node/auth-token related steps in this workflow.
rg -n "setup-node|NODE_AUTH_TOKEN|_authToken|npm install -g npm@" .github/workflows/release.yamlRepository: legioncodeinc/hivedoctor
Length of output: 202
🌐 Web query:
npm install -g ignore-scripts lifecycle scripts default behavior npm auth token setup-node OIDC trusted publishing
💡 Result:
As of July 2026, significant changes to npm's default security behavior regarding lifecycle scripts and authentication are in effect, particularly with the release of npm v12 [1][2]. Lifecycle Scripts and ignore-scripts Historically, npm ran lifecycle scripts (preinstall, install, postinstall) automatically by default, and the --ignore-scripts flag was the primary method to prevent this execution [3][4][1]. As of npm v12, lifecycle scripts are blocked by default [5][1][2]. Projects must now opt-in to running these scripts for specific dependencies by using an allowlist (managed via npm approve-scripts and the allowScripts field in package.json) [6][7][1][2]. When using --ignore-scripts (on older versions or as a legacy configuration): - It instructs npm not to execute lifecycle scripts during installation [3][4]. - It does not prevent explicit commands such as npm start, npm stop, or npm run-script from executing their respective scripts, though it may skip associated pre- or post-scripts [3][8]. Trusted Publishing (OIDC) and Authentication Trusted publishing allows publishing to npm from CI/CD environments (like GitHub Actions) using OpenID Connect (OIDC) instead of static long-lived tokens [9][10]. This requires npm version 11.5.1 or later [10]. Configuration Conflict: A common issue arises when using actions/setup-node with the registry-url configuration in environments intended for OIDC publishing [11][12]. If registry-url is set without an explicit NODE_AUTH_TOKEN, the action may write a placeholder _authToken entry to the.npmrc file [11][12]. When npm sees this entry—even if the token value is effectively empty—it may attempt traditional token-based authentication and fail to initiate the OIDC exchange, resulting in ENEEDAUTH or E404 errors [11][12]. To resolve this when using Trusted Publishing: - Avoid using the registry-url input in setup-node if it causes this conflict [12]. - Ensure your workflow has id-token: write permissions [10]. - Verify that your Trusted Publisher configuration on npm (matching the repository/workflow) is correctly set [9][10].
Citations:
- 1: https://www.aikido.dev/blog/npm-v12-block-postinstall
- 2: https://news.lavx.hu/article/npm-v12-will-turn-off-lifecycle-scripts-and-git-installs-by-default
- 3: https://docs.npmjs.com/cli/v11/commands/npm-rebuild/
- 4: https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/NPM_Security_Cheat_Sheet.md
- 5: https://github.com/npm/cli/pull/9424/files
- 6: [RFC] Make install scripts opt-in npm/rfcs#868
- 7: https://github.com/npm/rfcs/blob/main/accepted/0054-make-scripts-install-opt-in.md
- 8: .npmrc option
ignore-scriptsis breakingnpm runnpm/npm#10675 - 9: https://docs.npmjs.com/trusted-publishers/
- 10: actions/setup-node@670825a
- 11: registry-url writes _authToken line that breaks npm Trusted Publisher OIDC when no NODE_AUTH_TOKEN is set actions/setup-node#1551
- 12: actions/setup-node registry-url interferes with OIDC trigger — example workflow fails with ENEEDAUTH when no NODE_AUTH_TOKEN npm/documentation#1960
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/release.yaml')
lines = p.read_text().splitlines()
for i in range(220, 311):
if i <= len(lines):
print(f"{i:4d}: {lines[i-1]}")
PY
printf '\n-- matches --\n'
grep -nE 'setup-node|NODE_AUTH_TOKEN|_authToken|npm install -g npm@' .github/workflows/release.yaml || trueRepository: legioncodeinc/hivedoctor
Length of output: 6078
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read just the relevant workflow step order with a small parser.
python3 - <<'PY'
from pathlib import Path
import re
text = Path('.github/workflows/release.yaml').read_text().splitlines()
for idx, line in enumerate(text, 1):
if any(k in line for k in ('setup-node', 'NODE_AUTH_TOKEN', '_authToken', 'npm install -g npm@', 'trusted publishing', 'enable oidc', 'Upgrade npm')):
print(f"{idx}: {line}")
PYRepository: legioncodeinc/hivedoctor
Length of output: 919
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/release.yaml')
for i, line in enumerate(p.read_text().splitlines(), 1):
if 240 <= i <= 290:
print(f"{i:4d}: {line}")
PYRepository: legioncodeinc/hivedoctor
Length of output: 3155
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for path in Path('.').rglob('*'):
if path.is_file() and path.suffix in {'.yaml', '.yml', '.json', '.md', '.sh', '.js', '.ts'}:
try:
text = path.read_text()
except Exception:
continue
if 'NODE_AUTH_TOKEN' in text or '_authToken' in text:
print(path)
for i, line in enumerate(text.splitlines(), 1):
if 'NODE_AUTH_TOKEN' in line or '_authToken' in line:
print(f" {i}: {line}")
PYRepository: legioncodeinc/hivedoctor
Length of output: 520
🌐 Web query:
npm install -g lifecycle scripts install scripts --ignore-scripts documentation global install public package registry auth token setup-node dummy _authToken
💡 Result:
The npm configuration settings and practices you inquired about relate to managing security during installation and authentication in automated environments. Lifecycle Scripts and --ignore-scripts Lifecycle scripts (such as postinstall) execute arbitrary commands during the package installation process [1][2]. While these scripts are useful for build tasks like native compilation, they also represent a significant security risk, as malicious packages can use them to execute commands on your system [3][1]. The --ignore-scripts flag is used to prevent the execution of these lifecycle scripts during npm install commands [4][5]. It can be passed as a command-line argument: npm install --ignore-scripts To make this the default behavior for all installations, you can add it to your.npmrc file: ignore-scripts=true Please note that while --ignore-scripts mitigates many risks, it is not a complete security solution. Some vulnerabilities have been identified where specific npm processes may still bypass this flag [6]. Additionally, it will break packages that rely on scripts for necessary compilation [2]. Public Registry Authentication and setup-node When using GitHub Actions, the setup-node action is commonly used to configure authentication for the npm registry [7][8]. The action automates the creation of an.npmrc file containing the registry URL and an _authToken placeholder [9]. In traditional token-based authentication, you provide a secret (typically stored as NODE_AUTH_TOKEN) which is injected into the.npmrc file [7][8]. The configuration in.npmrc generally looks like this: //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} Dummy Token and OIDC The actions/setup-node action may automatically write a placeholder for the _authToken even if no specific secret is provided [9][10]. This behavior is designed to support traditional auth flows [10]. However, this can sometimes interfere with modern npm Trusted Publisher (OIDC) authentication flows, as the presence of an _authToken line might cause npm to skip the OIDC token exchange process [10]. If you are using OIDC-based publishing and encounter issues, you can work around the presence of the dummy token by unsetting or clearing the NODE_AUTH_TOKEN environment variable before running your publish command [11]: NODE_AUTH_TOKEN="" npm publish Using modern versions of Node.js (v24+) and npm (v11+) is recommended for native support of the OIDC trusted publishing flow [11].
Citations:
- 1: https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/NPM_Security_Cheat_Sheet.md
- 2: https://safeguard.sh/resources/blog/npm-install-script-security
- 3: https://blog.npmjs.org/post/141702881055/package-install-scripts-vulnerability
- 4: https://docs.npmjs.com/cli/v9/using-npm/config/
- 5: https://docs.npmjs.com/cli/v9/commands/npm-install/?v=true
- 6: GHSA-wr8v-3jqh-9x36
- 7: https://github.com/github/docs/blob/main/content/actions/tutorials/publish-packages/publish-nodejs-packages.md
- 8: https://docs.github.com/en/enterprise-server@3.2/actions/publishing-packages/publishing-nodejs-packages
- 9: https://github.com/actions/setup-node/blob/main/src/authutil.ts
- 10: registry-url writes _authToken line that breaks npm Trusted Publisher OIDC when no NODE_AUTH_TOKEN is set actions/setup-node#1551
- 11: Don't default NPM_AUTH_TOKEN to support NPM OIDC actions/setup-node#1440
🌐 Web query:
actions/setup-node registry-url _authToken NODE_AUTH_TOKEN placeholder .npmrc npm install -g public package ENEEDAUTH
💡 Result:
The ENEEDAUTH error occurs because npm detects an _authToken configuration in your .npmrc file and assumes it must use that token for authentication, even if it is empty [1][2]. When you use the registry-url input in the actions/setup-node action, it automatically generates a temporary .npmrc file containing a placeholder line: //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} [1][2][3]. If you do not provide a valid NODE_AUTH_TOKEN secret in your environment, this placeholder expands to an empty string [2]. Because the file now explicitly contains an empty _authToken, the npm CLI incorrectly decides that authentication is "configured" and skips its automatic mechanisms (such as Trusted Publisher OIDC or standard interactive login), leading to the ENEEDAUTH or E404 error [1][2]. To resolve this, choose one of the following approaches: 1. Use Trusted Publishers (OIDC): If you are using GitHub's Trusted Publishers (which does not require a long-lived secret), you must prevent actions/setup-node from writing the conflicting auth line [1][2]. Currently, the most reliable way to achieve this is to avoid using the registry-url input in the setup-node step [2]. Instead, configure the registry manually or ensure your workflow is configured in a way that doesn't trigger the automatic .npmrc generation [2]. 2. Provide the Token: If you are using a classic token-based flow, ensure the NODE_AUTH_TOKEN environment variable is correctly set in your step's env block [4][5]: jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.YOUR_NPM_TOKEN }} 3. Manual Configuration: If you need custom registry behavior or are having trouble with the action's automatic file generation, you can manually configure the registry and auth without the registry-url input [2]. You can use npm config set commands or create a local .npmrc file directly within your workflow steps, ensuring it is properly populated with your secrets [4][6][7]. Regarding npm install -g: Be aware that global installations in CI environments often ignore user-level .npmrc files or have different registry resolution behaviors depending on the OS and npm version [7]. It is generally recommended to install packages locally in your project or use npx to execute global-style tools [7].
Citations:
- 1: registry-url writes _authToken line that breaks npm Trusted Publisher OIDC when no NODE_AUTH_TOKEN is set actions/setup-node#1551
- 2: actions/setup-node registry-url interferes with OIDC trigger — example workflow fails with ENEEDAUTH when no NODE_AUTH_TOKEN npm/documentation#1960
- 3: https://github.com/actions/setup-node/blob/main/src/authutil.ts
- 4: https://stackoverflow.com/questions/75357158/github-actions-npm-publish-fails-with-err-code-eneedauth
- 5: [BUG] ENEEDAUTH issue when running "npm publish" within a monorepo on a ci environment npm/cli#6184
- 6: https://pnpm.io/10.x/npmrc
- 7: https://stackoverflow.com/questions/73985761/install-private-gitlab-npm-package-on-gitlabci-globally-npm-i-g
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for path in Path('.').rglob('*'):
if not path.is_file():
continue
try:
text = path.read_text()
except Exception:
continue
if 'NODE_AUTH_TOKEN' in text or '_authToken' in text:
print(path)
for i, line in enumerate(text.splitlines(), 1):
if 'NODE_AUTH_TOKEN' in line or '_authToken' in line:
print(f" {i}: {line}")
PYRepository: legioncodeinc/hivedoctor
Length of output: 520
🌐 Web query:
npm@11.6.2 package scripts lifecycle preinstall postinstall registry package metadata
💡 Result:
In npm version 11.6.2, lifecycle scripts and package metadata registry interactions function according to established npm standards [1][2]. Lifecycle scripts allow users to run arbitrary commands at specific stages of a package's installation or lifecycle [3][4]. The preinstall and postinstall scripts are defined within the scripts object of a package.json file [3][5]. When running npm install, these scripts are executed in the sequence: preinstall, install, and then postinstall [3][6]. These scripts are intended primarily for tasks like compilation that must occur on the target architecture [3][7]. Regarding registry interaction and metadata, the npm registry provides metadata documents for packages, which include information parsed from the package.json file, such as version, dependencies, and scripts [8][9]. When a package is published, the registry processes this metadata [8]. For efficient installation, the registry supports an abbreviated metadata format that includes an allow-list of fields; specifically, the abbreviated version object contains a boolean field, hasInstallScript, which is set to true if that version includes install scripts [8]. This allows the npm client to determine if lifecycle scripts need to be executed without downloading the full package metadata [8]. NPM version 11.6.2, released on October 8, 2025, focused primarily on bug fixes, code cleanup, and documentation updates rather than altering the core architecture of lifecycle scripts or registry metadata handling [2][10].
Citations:
- 1: https://docs.npmjs.com/cli/v11/using-npm/scripts/
- 2: https://github.com/npm/cli/releases/tag/v11.6.2
- 3: https://docs.npmjs.com/cli/v7/using-npm/scripts/
- 4: https://docs.npmjs.com/cli/v9/configuring-npm/package-json/
- 5: https://docs.npmjs.com/files/package.json/
- 6: https://docs.npmjs.com/cli/v8/using-npm/scripts/?v=true
- 7: https://docs.npmjs.com/cli/v6/using-npm/scripts/
- 8: https://github.com/npm/registry/blob/main/docs/responses/package-metadata.md
- 9: https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md
- 10: https://github.com/npm/cli/blob/9d77b84ce961a28941af8b1a597a03e308828cd4/CHANGELOG.md
Move the auth-strip step before the npm upgrade. The current order leaves setup-node’s scaffolded _authToken in place for the first registry-touching npm command, which can derail the release flow; --ignore-scripts is optional hardening, not the core fix.
🤖 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.yaml around lines 257 - 268, The release
workflow’s OIDC auth cleanup is happening too late, so the scaffolded
`_authToken` from setup-node can still affect the first npm command. Move the
“Enable OIDC trusted publishing (strip scaffolded auth token)” step to run
before the “Upgrade npm” step in the release workflow, keeping the auth-strip
logic in place and leaving any --ignore-scripts hardening optional.
Summary
Applies the same least-privilege release split that CodeRabbit required on hivenectar PR #11 to hivedoctor's release pipeline (this repo was explicitly opened for modify-as-needed upgrades).
releasejob, which rannpm ci, the full gate, the build, and all guards while holdingcontents: write+id-token: write, is now two jobs:contents: read): npm ci,npm run ci, pack-check, the telemetry-keyed build, tag/version guard, publishability preflight, publish-mode resolution, then uploadsbundle/as an artifact.contents: write+id-token: write): downloads the gate-builtbundle/and publishes it verbatim with--ignore-scripts, soprepacknever reruns and no repository or third-party code executes under the OIDC publish identity.pack:checktriggers a keylessprepackrebuild, so the telemetry-keyed build runs after it, guaranteeing the uploaded artifact carries the baked PostHog define.Test plan
gate: contents: read;publish: contents: write, id-token: write).workflow_dispatch(dry_run defaults to true) after merge to prove the artifact handoff and the--ignore-scriptspack path before the next real tag.Summary by CodeRabbit
Bug Fixes
Chores