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.
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. A stolen CI token stages a malicious version but never promotes it.
From Andrey's follow-up:
I already moved
nanoidandnanospyto 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:
- name: Publish npm package
run: npm stage publish
PostCSS followed through
Andrey said "a week or two." It took nine days. As of June 27, four of the seven packages under the ai npm account have Staged Publishing enabled:
| Package | Weekly downloads | Staged Publishing | Score |
|---|---|---|---|
| postcss | 251M | β | 85 |
| nanoid | 207M | β | 92 |
| browserslist | 166M | β | 89 |
| autoprefixer | 61M | β | 89 |
| caniuse-lite | 171M | β | 81 |
| postcss-nested | 54M | β | 72 |
| postcss-js | 53M | β | 70 |
That's 685 million weekly downloads now behind a human approval gate. One GitHub issue, nine days, no drama.
Three more packages remain. When caniuse-lite, postcss-nested, and postcss-js adopt, the entire PostCSS ecosystem β 963 million weekly downloads β will be gated.
Check your dependencies
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.
The full PostCSS ecosystem audit data comes from Commit, which scores packages on behavioral signals rather than declared metadata.
Top comments (1)
Quick question on where the real protection sits. Staged Publishing stops a stolen CI token from promoting to latest, which is great, but the human at the gate is approving a version number and a green checkmark, not reading the staged diff line by line. So if the CI pipeline itself is running attacker code (which is exactly how TanStack and the Red Hat packages went down), the staged build looks completely legitimate and a tired maintainer clicks approve. Does the approval step surface anything that would actually catch a build that's been poisoned but still looks valid, like a diff against the last release or a summary of changed dependencies? The gate only really helps if the human has something concrete to say no to. Still a great outcome from one issue, nine days is fast for a change like this.