Build1 distinct publisher3 min readPublished
A release workflow signed provenance, reached sigstore, then took a 404 on a package its owner has published for months. The registry withholds existence on purpose, so the real fault, a runner bundling npm 10.8.2, never appears in the error.
The Engineer · Build desk
Follow any of these and your For You feed starts watching them — no settings page required.
build
The Mini Shai-Hulud worm passed provenance checks by running inside the release pipeline1 distinct publisher
build
The npm audit that works because it never installs the package1 distinct publisher
security
Reading OIDC tokens out of runner memory: ChainDrop and the poisoned build1 distinct publisher
build
Layered permissions, not just three GitHub tools, secure this dependency scanner1 distinct publisher
Compiled by The EngineerSomething wrong?How this is made
On a runner with npm 10.8.2 and a trusted publisher registered, `npm publish` does something boring. It reads the `.npmrc` that `actions/setup-node@v4` generated, which contains `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}` [7]. Nothing in that npm attempts an OIDC token exchange, because the file that implements it, `lib/utils/oidc.js`, is not in the tarball [12]. The PUT goes out with whatever the variable resolves to, and the publish is unauthenticated [14]. The registry answers an unauthorised write to an existing package with 404 rather than 403 [15], deliberately, since a 403 would confirm the package exists [16]. That is how you get told a package you own, with published versions, is not in the registry [6].
Two artifacts in the failing run, as logged in the dev.to writeup, argue against that reading. Provenance signing had already succeeded and posted to the sigstore transparency log before the error [5], and that needs the GitHub Actions id-token, so `id-token: write` is demonstrably working [17]. It is a different feature. Provenance uses the id-token to sign a sigstore attestation; Trusted Publishing uses it to exchange for a registry auth token; the two are separate code paths introduced in separate npm versions [18].
The step environment is the worse of the two. It prints `NODE_AUTH_TOKEN: XXXXX-XXXXX-XXXXX-XXXXX`, which reads as a masked secret and is in fact literal X characters, exported by `src/authutil.ts` on the `v4` tag so npm does not complain about a missing variable [8][9]. A dummy credential that spells itself out in X's is a convincing impression of a redaction. The author's first theory was a stale `NPM_TOKEN` shadowing OIDC, and removing it plus an `unset NODE_AUTH_TOKEN` changed nothing [19]. In the npm that has the feature it could not have been the cause anyway: `oidc()` is called before credentials are read and overwrites the auth token directly [20].
This is your failure only if all of the following hold. You are pinned to `setup-node@v4`, which still exports the dummy value where the action's `main` branch exports it only when you supply one [10]. You accepted `node-version: 20` and its bundled npm 10.8.2 [13]. And the package already exists, because the 404 is about write permission on something present [15].
The version probe in the writeup deserves one caveat. Extracting each tarball and looking for `lib/utils/oidc.js` cleanly separates npm 10 from npm 11.5.x [12], but the same probe finds the file in 11.5.0 while the text dates the feature to 11.5.1 [11][22]. It confirms whether the code path exists at all; it does not pinpoint which release added it.
What follows raises a version floor on the runner, leaving your workflow's permissions block untouched. Trusted Publishing needs npm 11.5.1 or later [11], Node 20 gives you 10.8.2 [13], so the release job needs an explicit npm install ahead of publish and an assertion on the version it actually resolved [21].
Ranked by verification strength, evidence, and original report placement.
npm 10 has no OIDC implementation at all and never attempts the token exchange; it falls back to the _authToken in the generated .npmrc, which resolves to nothing useful, and publishes unauthenticated.
npm is winding down 2FA-bypass granular access tokens; since 31 July 2026 they can no longer perform account, organisation, or package management operations, which includes editing trusted publishing configuration.
In January 2027 npm granular access tokens lose direct publish, leaving them able to read private packages and stage a publish that a maintainer then approves with 2FA.
The replacement is OIDC Trusted Publishing: you register a GitHub repo and workflow on npmjs.com, and the workflow authenticates with a short-lived token instead of a stored secret.
After the author migrated to Trusted Publishing, the release workflow failed four times in a row, and the error message pointed nowhere near the actual problem.
Distinct publishers with included, body-backed reporting in this cluster.
1 article · September 6, 2026
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Re-runnable, one author
Almost nothing here rests on assertion. The 404 body, the setup-node export with its own code comment, the two lines of publish.js that settle the call order, and a four-line loop that unpacks npm 10.8.2 against three 11.x releases are all quoted and can be reproduced from public artefacts. The soft joint is the version boundary: the same probe that proves 10.8.2 has no OIDC code also finds it in 11.5.0, one release below the 11.5.1 the post names, with no changelog cited either way.
One package, one runner
The only migration on record is the author's own: chron-mcp moved to Trusted Publishing, broke four times on a Node 20 image, then published once npm 11.5.1 or later was installed on Node 24. npm's dated retirement of granular tokens implies a large migration standing behind this, and none of it is counted here — no second maintainer reporting the same 404, no registry figures, no read on how many workflows still pin setup-node@v4 with node-version 20.
A shade past the probe
The headline behaviour, npm 10 falling through to a useless _authToken and publishing unauthenticated, is inferred from consistent artefacts rather than watched on the wire; the only server response quoted anywhere is the 404. Set against that, the piece gives away the fact that damages it most, that --provenance was wrong from the first commit because the source repository is private, and it resists the leaked-credential reading of the XXXXX token that a louder writeup would have run with.
Nothing being sold
A maintainer debugging their own package on a platform with no editorial layer, criticising both npm and an official GitHub action, and recommending a two-line workflow change with no product attached. The one pull worth naming is self-presentation: the package name travels with the war story, and the piece is its own reviewer. That is a weak distorting force next to a vendor writing up its own release.
Verifiable, unverified
Anyone with a terminal can confirm the version boundary and the placeholder token, which is why a lone self-published post scores this well. It stays under seventy because npm and the setup-node maintainers say nothing here, the 11.5.1 figure has no citation behind it, and the claim that the failed publish went out with no credentials is reasoning from three artefacts rather than an observed request.