All articles
Dev ToolsBy Petru Popa · 6 min read

npm Trusted Publishing Is Now an Or Across Ten Configurations

GitHub moved three npm publishing changes to general availability on September 3, 2026. The one with the headline is that a package can now hold more than one trusted publishing configuration. The one that changes your threat model sits further down the same note: a publish or stage is authorized if the incoming OIDC token matches any one configuration, configurations never restrict one another, and evaluation order is not guaranteed.

npm trusted publishing has stopped being a lock on one door. It is a list of doors, and the check is an or.

What npm trusted publishing looks like now

Three items went generally available together: multiple trusted publishing configurations per package, a rule that staged packages cannot be approved until malware scanning finishes, and per-version staging history on the package versions tab. The approval control stays disabled while a scan runs, and the page refreshes its status once a minute.

The npm documentation puts a number on the first item that the changelog does not: a package can have up to ten trusted publishers configured at the same time. Each one is independent and additive, with its own repository, workflow, and environment criteria, added and removed from the package settings page. Supported providers are GitHub Actions on GitHub-hosted runners, GitLab CI/CD on GitLab.com shared runners, and CircleCI cloud; self-hosted runners are not supported. Trusted publishing itself needs npm CLI 11.5.1 or later on Node 22.14.0 or higher, and staged publishing needs 11.15.0 or later.

The assumption this invalidates

The assumption is that configuring trusted publishing is a thing you do once, and afterwards the package is hardened. That held while a package could only have one configuration. It does not hold across ten, because a disjunction is only as strong as its loosest term. The publishing posture of a package is now the weakest of its configurations, and adding one can only widen the set of workflows that can publish. Nothing in the model lets a configuration constrain another.

Two documented details make that worse than it sounds. A connection's provider and required fields are fixed once it is created, so correcting a repository, a workflow filename, or a provider means deleting the connection and building a new one. And the default for what a configuration may do depends on when it was made. Configurations created before May 20, 2026 were set to allow direct publishing only. Configurations created before September 3, 2026 require an explicit choice of at least one allowed action. Configurations created after September 3, 2026 are set to allow staged publishing, with direct publishing as an opt-in. The security posture of a package therefore encodes the calendar, and the configurations that skip the approval step entirely are the oldest ones.

This is the same shape as the permission and audit-logging work that gets skipped between a working prototype and a production system, which we have written about in the gap between a PoC and production. The publish path is a permission boundary, and it just became a set of them.

The thing the announcement did not say

Go to the documentation and the interesting asymmetry is provenance. When you publish through trusted publishing from GitHub Actions or GitLab CI/CD, npm generates and publishes a provenance attestation automatically, with no flag required. That happens only when three conditions all hold: the publish is via OIDC, the repository is public, and the package is public. Private repositories get no provenance even when the published package is public. CircleCI trusted publishing gets none at all.

Put that beside the guidance not to build logic that depends on which configuration matched. The registry's durable record of where a version was built is the attestation — and for a CircleCI configuration, or any configuration wired to a private repository, there is no attestation to read. The configuration whose publishes leave the least evidence is exactly the one an audit will find hardest to reason about. Provenance can also be turned off from inside the repository, through NPM_CONFIG_PROVENANCE, an .npmrc entry, or publishConfig in package.json.

The OpenSSF working-group document that npm cites as the industry standard for this feature names the matching structural risk from the other side. Trust policies built on human-readable names are exposed to rename and resurrection attacks, because names are mutable while the underlying identifiers are not; the recommended mitigation is resolving names to IDs and persisting those. The document also states the limit of that mitigation directly: a GitHub workflow file name has no ID. npm's GitHub Actions form asks for the organization, the repository, and the workflow filename — filename only, case-sensitive, extension included.

What to do this quarter

Not a policy. An inventory, then a cleanup.

  1. List the trusted publishers on every package you own, from Settings and Trusted publishing on npmjs.com. Write one sentence per configuration saying which release it exists to run.
  2. Any package where the configuration count exceeds the number of release workflows you can name is a finding. Delete the remainder.
  3. Move every configuration to stage-only. Anything created before May 20, 2026 was set to direct publish only, so that is where to start; the docs do not say whether allowed actions can be changed after creation, and deleting and recreating the connection is the path they do describe.
  4. Make approval a real review: npm stage list, then npm stage view <stage-id> and npm stage download <stage-id>, then npm stage approve <stage-id>. Approval requires 2FA in the CLI or on the site; npm stage publish does not.
  5. Set Publishing access to require two-factor authentication and disallow tokens. It only affects token authentication; OIDC configurations keep working.

One planning caveat: staged publishing cannot create a package. The package must already exist on the registry, so a first release still goes out by another path.

What would show this is wrong

The argument rests on the authorization check being a disjunction with no precedence. If npm documents an order, a deny rule, or a most-restrictive-wins evaluation across configurations, the claim that adding one can only widen the surface fails, and the inventory above becomes busywork. Today the changelog says the opposite in as many words. The second half weakens if provenance ships for CircleCI or for private repositories, which would close the evidence gap without anyone changing a configuration.

Ready to start?

Turn this into a plan for your team.

One week, fixed fee: a working session with your team, a prioritized use-case backlog, and an ROI model for the opportunities worth chasing.

Book an AI Opportunity Sprint