nanoid Adopted Staged Publishing After a Technical Debate About CI Provenance

Andrey Sitnik didn’t just accept the suggestion. He argued CI provenance makes things worse. Then he shipped the fix anyway.

On June 18, 2026, I filed postcss/postcss#2096 about OIDC provenance for PostCSS. The ai npm account—one person, Andrey Sitnik—publishes PostCSS, nanoid, Autoprefixer, browserslist, and caniuse-lite. Combined: over 900 million weekly downloads through a single publish credential.

Andrey’s first reply was not agreement. It was a correction.

“CI-as-publisher increased the attack risks”

From his comment:

Provenance wouldn’t save from all of that supply chain attack. The old CI-only based provenance was also a reason of TanStack Shai-Hulud attack.

CI-as-publisher increased the attack risks compared to 2FA manual publishing. TanStack was attacked only because they publish by CI and it was a token on CI.

He is right. TanStack’s May 2026 compromise came through GitHub Actions cache poisoning. The attacker got an OIDC token from the CI runner and used it to publish. The provenance attestation was valid—the package was built by TanStack’s CI pipeline. The CI pipeline was just also running the attacker’s code.

Red Hat’s June 1 compromise proved the same pattern. Thirty-two packages published through a compromised GitHub account’s CI pipeline. All 32 had valid SLSA provenance attestations. The provenance was technically correct. The code was not.

Andrey’s argument: if you publish manually with hardware-bound 2FA (passkey, YubiKey), the attacker needs physical access to your device. If you publish through CI, the attacker needs a GitHub token—a much larger attack surface.

The resolution: Staged Publishing

npm’s Staged Publishing splits the problem: CI builds and stages. A human approves before latest moves. The CI pipeline does the deterministic work (build, attest, stage). The human provides the trust decision (2FA confirmation that the staged version should go live). A stolen CI token stages a malicious version but never promotes it.

From Andrey’s follow-up:

I already moved nanoid and nanospy to the new process, we can test them.

PostCSS will be done in a week or two (too many other open source projects) 😅

The diff

nanoid’s release.yml, updated June 18 at 19:16 UTC:

- name: Publish npm package
  run: npm stage publish

nanospy’s release.yml, updated June 19 at 11:06 UTC:

- name: Publish npm package
  run: npm stage publish

nanoid serves 221 million weekly downloads. It is a transitive dependency of virtually every modern Node.js project. One npm account, one credential—now gated behind a human approval step.


Verification

The adoption is independently verifiable. No trust required.

npx proof-of-commitment nanoid nanospy postcss

Or query the API:

curl https://poc-backend.amdal-dev.workers.dev/api/score/npm/nanoid \
  | jq '{hasStagedPublishing, commitmentScore, weeklyDownloads: .recentWeeklyDownloads}'

nanoid returns hasStagedPublishing: true, score 92. nanospy returns hasStagedPublishing: true. PostCSS still returns false—that will change when Andrey gets to it.


Why this matters more than Hono

Hono’s adoption was faster—33 hours from issue to merge. But Andrey’s adoption is more interesting because he started by pushing back.

He didn’t disagree with the risk assessment. He disagreed with the mitigation. OIDC provenance, which our original issue recommended, shifts the attack surface to CI. Staged Publishing addresses his exact objection: CI does the build, a human does the approval. Both sides of the argument improved the outcome.

The broader pattern: one npm account controls 964M weekly downloads. nanoid and nanospy are now gated. PostCSS, Autoprefixer, browserslist, caniuse-lite are not. When they are, over 700 million weekly downloads move behind the approval step.


Monitor your dependencies for these changes

npx proof-of-commitment

Scans your lockfile. Flags single-publisher packages at scale. Shows provenance, Staged Publishing, and dormant access status. When nanoid’s score went from 90 to 92 after adopting Staged Publishing, the CLI picked it up automatically—no human re-tagged anything.

Free API key watches 3 packages with weekly digest. Developer ($15/mo) watches 15 with daily scans and alerts when a CRITICAL dependency adopts the gate—or when a new one enters your tree.