Skip to content

Commit 563f4ab

Browse files
Migrate npm OIDC (#230)
* refactor: migrate to OIDC for npm package publishing Replace token-based authentication with OpenID Connect (OIDC) for enhanced security: - Remove NPM_TOKEN from changesets action environment - Enable automatic provenance generation (NPM_CONFIG_PROVENANCE) - Set NODE_AUTH_TOKEN to empty string to force OIDC authentication - Keep diagnostic step for troubleshooting auth issues This requires configuring npm Trusted Publisher on npmjs.com for the repository. * refactor: restructure publish workflow to support OIDC properly Split changesets/action into versioning-only step and separate publish step to avoid .npmrc token mutation that would block OIDC. This ensures npm uses Trusted Publisher authentication instead of legacy token-based auth. Key changes: - changesets/action now only runs version step (no publish input) - Added OIDC preflight step to scrub any auth tokens from .npmrc before publishing - Separated publish into dedicated step with pnpm changeset-publish - Added post-mortem diagnostics for troubleshooting publish failures The id-token: write permission remains enabled for OIDC token generation. Registry setup and package.json publish script already include provenance. * fix: harden OIDC workflow with publish gate and comprehensive preflight Address three critical risks identified during code review: 1. Gate publish step to run only when no changesets exist - Prevents wasteful runs and log noise when version PR is created - Ensures publish only runs on merge commits (when hasChangesets == 'false') - .github/workflows/publish.yaml:74 2. Harden preflight to scrub all .npmrc locations and auth forms - Iterate over all potential locations: NPM_CONFIG_USERCONFIG, ~/.npmrc, .npmrc - Remove both registry-scoped (_authToken, _auth=) and global (always-auth) forms - Prevents stray .npmrc from blocking OIDC authentication - .github/workflows/publish.yaml:45-52 3. Make whoami check robust with exit code instead of grep - Check npm whoami exit code (0 = logged in, non-zero = not logged in) - Avoids reliance on fragile message matching across npm versions - Verify registry config (including @openrouter scope) - .github/workflows/publish.yaml:62-71 4. Enhance post-mortem diagnostics - Log registry configuration for both global and @openrouter scope - Redact .npmrc contents to aid forensics without exposing secrets - Check all three potential .npmrc locations - .github/workflows/publish.yaml:92-101 With these changes, the workflow is hardened against OIDC authentication failures. * refine: use extended regex patterns for comprehensive .npmrc token scrubbing Improve sed patterns to handle edge cases in token removal: - Registry-scoped tokens: Allow optional whitespace around = sign sed -i -E '/\/\/registry\.npmjs\.org\/:(_authToken|_auth)\s*=/d' - Global tokens: Match _authToken or _auth anywhere on a line with leading whitespace sed -i -E '/^\s*(_authToken|_auth)\s*=/d' - Global always-auth: Case-insensitive with optional spacing sed -i -E '/^\s*[Aa]lways-[Aa]uth\s*=/d' This closes the gap where npm could treat whitespace-padded or variant token lines as a signal to use legacy token auth instead of OIDC. Extended regex (-E) enables more flexible matching without sacrificing readability. Addresses edge case identified during workflow review.
1 parent cae7fb4 commit 563f4ab

File tree

1 file changed

+67
-37
lines changed

1 file changed

+67
-37
lines changed

.github/workflows/publish.yaml

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,47 +29,77 @@ jobs:
2929
cache: pnpm
3030
- name: Install dependencies
3131
run: pnpm install --frozen-lockfile
32-
- name: Diagnose npm authentication
32+
- name: Create Release Pull Request or Publish
33+
id: changesets
34+
uses: changesets/action@v1
35+
with:
36+
version: pnpm changeset-version
37+
env:
38+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39+
40+
- name: OIDC preflight - scrub .npmrc and verify registry
3341
run: |
34-
echo "=== NPM Authentication Diagnostics ==="
35-
echo "Node version: $(node --version)"
36-
echo "npm version: $(npm --version)"
37-
echo "pnpm version: $(pnpm --version)"
38-
echo ""
39-
echo "=== Environment Variables ==="
40-
echo "NPM_TOKEN set: ${NPM_TOKEN:+yes (length ${#NPM_TOKEN})}"
41-
echo "NODE_AUTH_TOKEN set: ${NODE_AUTH_TOKEN:+yes (length ${#NODE_AUTH_TOKEN})}"
42-
echo "NPM_CONFIG_USERCONFIG: $NPM_CONFIG_USERCONFIG"
43-
echo ""
44-
echo "=== npm config ==="
45-
npm config list --json | jq '.registry, .["@openrouter:registry"], .["@openrouter/registry"]' || npm config list
46-
echo ""
47-
echo "=== .npmrc files ==="
48-
for rc in ~/.npmrc "$NPM_CONFIG_USERCONFIG" .npmrc; do
49-
if [ -f "$rc" ]; then
50-
echo "Found: $rc"
51-
wc -l "$rc"
42+
echo "=== OIDC Preflight ==="
43+
44+
# Remove auth tokens from all potential .npmrc locations to ensure OIDC is used
45+
for npmrc in "$NPM_CONFIG_USERCONFIG" ~/.npmrc .npmrc; do
46+
if [ -n "$npmrc" ] && [ -f "$npmrc" ]; then
47+
echo "Cleaning $npmrc of any existing auth tokens..."
48+
# Remove registry-scoped tokens (allow optional whitespace around =)
49+
sed -i -E '/\/\/registry\.npmjs\.org\/:(_authToken|_auth)\s*=/d' "$npmrc"
50+
# Remove global tokens in any form
51+
sed -i -E '/^\s*(_authToken|_auth)\s*=/d' "$npmrc"
52+
# Remove global always-auth (case-insensitive, allow spacing)
53+
sed -i -E '/^\s*[Aa]lways-[Aa]uth\s*=/d' "$npmrc"
5254
fi
5355
done
56+
57+
# Verify npm connectivity
58+
echo "Testing npm registry connectivity..."
59+
npm ping || exit 1
60+
61+
# Verify no token is active (should fail for OIDC to work)
62+
echo "Verifying no auth token is configured..."
63+
if npm whoami >/dev/null 2>&1; then
64+
echo "⚠ Warning: npm whoami succeeded without OIDC token"
65+
else
66+
echo "✓ Confirmed: npm whoami failed (OIDC will be used)"
67+
fi
68+
5469
echo ""
55-
echo "=== Auth token in registry ==="
56-
npm config get //registry.npmjs.org/:_authToken | head -c 10 && echo "***[rest redacted]"
70+
echo "Registry configuration:"
71+
npm config get registry
72+
npm config get @openrouter:registry || echo " (no @openrouter scope override)"
73+
74+
- name: Publish with OIDC
75+
if: steps.changesets.outputs.hasChangesets == 'false'
76+
run: pnpm changeset-publish
77+
env:
78+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
79+
80+
- name: Post-mortem diagnostics
81+
if: failure()
82+
run: |
83+
echo "=== Post-mortem Diagnostics ==="
84+
echo "Versions:"
85+
echo " Node: $(node --version)"
86+
echo " npm: $(npm --version)"
87+
echo " pnpm: $(pnpm --version)"
5788
echo ""
58-
echo "=== npm whoami ==="
59-
npm whoami 2>&1 || echo "FAILED"
89+
echo "Registry configuration:"
90+
echo " registry: $(npm config get registry)"
91+
echo " @openrouter scope: $(npm config get @openrouter:registry || echo '(inherited from global)')"
6092
echo ""
61-
echo "=== npm ping ==="
62-
npm ping 2>&1 || echo "FAILED"
93+
echo ".npmrc files status:"
94+
for npmrc in "$NPM_CONFIG_USERCONFIG" ~/.npmrc .npmrc; do
95+
if [ -n "$npmrc" ] && [ -f "$npmrc" ]; then
96+
echo " $npmrc:"
97+
echo " Lines: $(wc -l < "$npmrc")"
98+
echo " Auth lines: $(grep -c "_auth\|_token" "$npmrc" || echo "0")"
99+
echo " Content (redacted):"
100+
sed 's/\(_auth[^=]*=\).*/\1***REDACTED***/g; s/\(_token[^=]*=\).*/\1***REDACTED***/g' "$npmrc" | sed 's/^/ /'
101+
fi
102+
done
63103
echo ""
64-
echo "=== Verify package exists on npm ==="
65-
npm view @openrouter/ai-sdk-provider@latest version 2>&1 || echo "FAILED"
66-
env:
67-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
68-
- name: Create Release Pull Request or Publish
69-
uses: changesets/action@v1
70-
with:
71-
version: pnpm changeset-version
72-
publish: pnpm changeset-publish
73-
env:
74-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
104+
echo "Package availability:"
105+
npm view @openrouter/ai-sdk-provider@latest version 2>&1 || echo " (could not fetch)"

0 commit comments

Comments
 (0)