🚀 New: chi (χ) — an open-source autoresearch harness for fleets of LLM coding agents. Read the announcement.

npm Trusted Publishing Configs Now Expire in 48 Hours Unless a Publish Validates Them

npm trusted publishing configurations that have never published now expire after 48 hours, and issue_comment workflows are rejected. What the change defends against and what to check in your release pipeline.

Contents

If you set up an npm trusted publisher and do not complete a publish from it within two days, it stops working. That is the behavior GitHub announced on October 2: configurations that have never been validated by a successful publish expire 48 hours after creation and can no longer authorize publishing. The same change adds a second restriction, covered below, for workflows triggered by issue_comment.

This reads like housekeeping. It is closer to a fix for a specific hole in how trust is declared.

What a trusted publisher actually trusts

Trusted publishing replaces a long-lived npm token with OIDC. You tell npm which CI identity may publish a package, and the CLI exchanges the runner’s OIDC token for publish rights. Per the npm docs, for GitHub Actions the identity is the organization or user, the repository, the workflow filename (with extension, no path) and an optional environment name. The workflow itself needs id-token: write, and the docs list npm CLI 11.5.1 or later with Node 22.14.0 or higher.

The docs also state a detail that explains the new expiry: npm does not verify these settings when you save them. Errors only appear at publish time. For GitHub, repository.url in package.json must also match the repository exactly.

So at the moment you save the form, npm holds a claim of the form “this owner/repo/workflow may publish this package”, and it has not checked that the claim points at anything real or anything you control.

The hole the expiry closes

GitHub’s stated rationale is that the change “reduces the risk of trusting a repository or project name that has changed ownership”. Spell out the failure path and it is easy to see.

A trusted publisher is matched by name. Suppose you configure acme/widgets and release.yml, then never publish from it, perhaps because the project stalled or the workflow was renamed. Later, the acme/widgets name becomes available to someone else, through a rename, a transfer, or a deleted account being claimed. If the stale configuration still authorizes publishing, the new owner of that name has a valid OIDC identity that npm will accept for your package. No token was stolen. The trust you declared simply outlived the thing it described.

Requiring a first successful publish inside 48 hours shrinks the time a never-used claim can sit around. A configuration that has published at least once is validated and exempt from expiry, per GitHub’s post. That is a reasonable line: a publish proves the identity named in the config was, at least once, the real one.

I am inferring the attack path from GitHub’s one-sentence rationale and the docs. The changelog does not describe an incident or a specific exploit, and I have not tested the rejection behavior myself.

What the rules say you must do

From the changelog:

  • Unvalidated configurations expire 48 hours after creation and stop authorizing publishes.
  • Changing the repository or project identity of a configuration requires a new trust relationship with a fresh 48-hour window. Ordinary edits do not restart the deadline.
  • To recover an expired configuration, recreate it. Expired ones stay visible in the trusted publisher settings but do not count toward per-package limits, and other valid configurations on the same package are unaffected.
  • npm now rejects trusted publishing tokens from GitHub Actions issue_comment events, in addition to the existing pull_request_target restriction. Move publishing to an event such as push, release or workflow_dispatch.

Two other facts from the docs interact with this. Existing connections cannot be edited, only deleted and recreated, so fixing a typo in the workflow filename means starting a new 48-hour clock. And a package can have at most 10 trusted publishers.

Where this will bite

The issue_comment restriction is the one most likely to break a working pipeline. A comment-driven release ("/release" in a PR thread) is a plausible pattern for monorepos, and a comment event can be triggered by anyone allowed to comment. Rejecting OIDC publish tokens from that event removes a path where a lower-trust actor could cause a high-trust action. If you publish this way, the fix is to have the comment add a label or dispatch a workflow that runs on an allowed event. The docs make the environment name in the trusted publisher config optional; setting one lets you attach your own approval rules to the publish job.

The expiry is more likely to bite during migrations. A team moving many packages from tokens to trusted publishing could easily configure all of them in one afternoon and release them over the following week. Under the new rule, any package not published within 48 hours lapses and has to be recreated. Configure each package right before its first release, or do the first publish of each one immediately.

A related trap is workflow_call and workflow_dispatch. The docs say validation checks the calling workflow’s name, and both the parent and child workflows need id-token: write. If you wrap publishing in a reusable workflow, the filename you register is the caller’s, not the reusable one.

A short audit

  1. List the trusted publishers on each package and look for any that have never published. Those are the ones on the clock or already expired.
  2. Search your workflows for issue_comment triggers that reach a publish step.
  3. Check that repository.url in package.json matches the repository exactly, since npm will not tell you until publish time.
  4. For reusable workflows, confirm the registered filename is the caller’s and that id-token: write is set at both levels.

The model underneath all of this is worth keeping: a trusted publisher is a name-based claim that npm cannot check when you make it. The only thing that validates it is a real publish, so a claim that has not been exercised should be treated as a liability and not as configuration.

Sources