Skip to content

Commit a8548bc

Browse files
authored
ci: publish by trusted publishing again (#309)
Switch the publish workflow back to OIDC trusted publishing — no stored credential, provenance automatic. Documents the E404 signature that means the publisher is not registered. Requires the trusted publisher on npmjs.com against workflow filename publish.yml.
1 parent 073d14c commit a8548bc

1 file changed

Lines changed: 41 additions & 27 deletions

File tree

.github/workflows/publish.yml

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,26 @@
1010
# that failed on something transient; it is safe because a version already on
1111
# the registry is skipped rather than attempted.
1212
#
13-
# Authenticates with a stored npm automation token, in the repository secret
14-
# `NPM_TOKEN`. Trusted publishing (OIDC) would avoid the stored credential, but
15-
# it needs a trusted publisher registered against this workflow's filename on
16-
# npmjs.com, which can only be done through the web UI — and until that exists
17-
# npm rejects the publish as E404/no-permission, which is how v0.24.1 failed.
13+
# Authenticates by trusted publishing (OIDC) rather than a stored token: npm
14+
# trades the short-lived token GitHub mints for this specific workflow run for
15+
# permission to publish, so there is no long-lived credential in the repository
16+
# to leak, rotate or forget.
1817
#
19-
# Worth knowing when this is next revisited: npm is restricting tokens that
20-
# bypass 2FA for direct publishing, so the token path has a horizon.
21-
# https://gh.io/npm-gat-bypass2fa-deprecation
18+
# The other half of that trust lives on npmjs.com, under the package's Trusted
19+
# Publisher settings, and it is pinned to the *filename* of this workflow.
20+
# Renaming this file silently breaks publishing — npm will refuse the exchange
21+
# because the run no longer matches what was configured.
22+
#
23+
# What that looks like when it goes wrong, since the error names neither OIDC
24+
# nor trusted publishing:
25+
#
26+
# npm error code E404
27+
# npm error 404 Not Found - PUT https://registry.npmjs.org/moshcode
28+
# npm error 404 ... could not be found or you do not have permission
29+
#
30+
# There is no credential at all in that case, and npm reports it as if the
31+
# package did not exist. If you see it, the publisher is not registered, or is
32+
# registered against a different workflow filename.
2233
name: publish
2334

2435
on:
@@ -28,9 +39,9 @@ on:
2839

2940
permissions:
3041
contents: read
31-
# Still needed with token auth: provenance is signed with a short-lived OIDC
32-
# token even though the publish itself authenticates with NPM_TOKEN. Without
33-
# it `--provenance` fails.
42+
# The whole basis of the exchange: this is what lets the run mint the OIDC
43+
# token npm authenticates against. Without it there is no credential at all
44+
# and publishing fails outright.
3445
id-token: write
3546

3647
jobs:
@@ -51,6 +62,22 @@ jobs:
5162
cache: pnpm
5263
registry-url: https://registry.npmjs.org
5364

65+
# Node 22 bundles npm 10, which predates trusted publishing and would fall
66+
# back to looking for a token that no longer exists — an auth failure that
67+
# reads as a credential problem rather than a version one. 11.5.1 is the
68+
# floor; the check below says so plainly if that ever regresses.
69+
- name: Install an npm that understands trusted publishing
70+
run: |
71+
npm install -g npm@latest
72+
VERSION="$(npm --version)"
73+
MINIMUM=11.5.1
74+
echo "npm $VERSION"
75+
# Lowest of the two must be the minimum, or this npm is older than it.
76+
if [ "$(printf '%s\n%s\n' "$MINIMUM" "$VERSION" | sort -V | head -n1)" != "$MINIMUM" ]; then
77+
echo "::error::npm $VERSION cannot use trusted publishing — $MINIMUM or later is required"
78+
exit 1
79+
fi
80+
5481
- run: pnpm install --frozen-lockfile
5582

5683
# Publishing is the one action here that cannot be taken back — npm will
@@ -95,24 +122,11 @@ jobs:
95122
echo "already=false" >> "$GITHUB_OUTPUT"
96123
fi
97124
98-
# Said plainly here, rather than as the E404/no-permission npm otherwise
99-
# returns partway through a release — an error that reads as "the package
100-
# does not exist" rather than "there is no credential".
101-
- name: Require an npm token
102-
if: steps.published.outputs.already == 'false'
103-
env:
104-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
105-
run: |
106-
if [ -z "$NPM_TOKEN" ]; then
107-
echo "::error::NPM_TOKEN is not set — add an npm automation token as a repository secret named NPM_TOKEN"
108-
exit 1
109-
fi
110-
125+
# No token, and no `--provenance` either: publishing through trusted
126+
# publishing generates and attaches the attestation on its own.
111127
- name: Publish
112128
if: steps.published.outputs.already == 'false'
113-
run: npm publish --access public --provenance
114-
env:
115-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
129+
run: npm publish --access public
116130

117131
- name: Confirm the registry has it
118132
if: steps.published.outputs.already == 'false'

0 commit comments

Comments
 (0)