> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# GitHub's 48-Hour Fix for npm Trusted Publishing Hijacks
- URL: https://debugly.dev/npm-trusted-publishing-hijack/
- Published: 2026-10-14T15:00:00.000Z
- Updated: 2026-10-17T05:47:51.000Z
- Description: A flaw in npm trusted publishing let attackers push malicious versions of 1,500+ packages without credentials. GitHub's fix is a 48-hour validation window.
- Author: Rohit Bhadani
- Tags: Security, DevOps

Trusted publishing was supposed to be the thing that killed long-lived npm tokens. No more secrets in GitHub Actions, no more token rotation, just an OIDC identity handshake between your CI runner and the registry. Then researchers found that the handshake trusted the wrong thing, and for 1,500 packages or more, an attacker could push a new version of someone else's package without ever touching their credentials. GitHub's response, shipped this week, is a 48-hour validation window: if your trusted publishing configuration is not validated by a successful publish within 48 hours of creation, it expires. Changing the repository or project identity requires starting the trust relationship over, and a fresh 48-hour clock starts again.

The details here come from the npm security advisory and the GitHub changelog published 13 October, cross-referenced with reporting from The Hacker News and GitHub's own engineering disclosure.

## How the hijack worked

Trusted publishing replaces a long-lived npm token with a short-lived OIDC token issued by the CI platform. The npm registry verifies the token against a configuration that says which repository and which workflow are allowed to publish which package. The flaw was in the gap between creating that configuration and the first time it was actually used.

During that gap, the configuration pointed at a repository identity that had not been verified by a successful publish. An attacker who could control a repository with the same name, or who could change the project identity in a way the configuration still matched, could claim the trust relationship before the legitimate owner did. The result was a malicious version of the package published under the legitimate owner's namespace, with no credential theft required.

The scale is the part that matters. Fifteen hundred packages had unvalidated trusted publishing configurations, and every one of them was a window. The window was not theoretical: it was a configuration state that the registry accepted as valid, and the registry had no way to distinguish the legitimate owner from the faster attacker.

## The 48-hour fix and what it actually does

GitHub's change has three parts. First, unvalidated trusted publishing configurations expire 48 hours after creation if no successful publish has occurred. Second, changing the repository or project identity in a configuration requires a new trust relationship with a fresh 48-hour window, so the identity cannot drift after validation. Third, npm now rejects trusted publishing tokens from GitHub Actions `issue_comment` events, alongside the existing `pull_request_target` restriction.

The first part is the fix that matters. It converts the gap between configuration creation and first use from an indefinite window into a two-day window, and it does so by making the default state untrusted until a publish proves otherwise. That is a good security design, and it is worth naming because it inverts the usual pattern. Most trust systems trust by default and revoke on failure; this one distrusts by default and validates on success.

The second part closes the identity-drift attack, where a repository changes ownership or visibility after the configuration was validated. The existing trust is invalidated, and the new identity must prove itself again. That is the class of fix that [an agent inheriting an engineer's permissions](https://debugly.dev/agent-inherited-the-engineers-permissions/) would have needed: a trust relationship that is scoped to the identity that earned it, not the identity that later claimed it.

## Why this matters for CI/CD supply chains

The npm supply chain has been a recurring target all year. The GhostAction and ChainDrop campaigns that I covered in the GitLab context compromised hundreds of developer GitHub accounts and tens of thousands of repositories, and the tensorlake worm used Ethereum smart contract data for C2 addresses, a technique that defeats domain-based blocking. Trusted publishing was the registry-side answer to the credential-theft problem that made those campaigns possible, and the 1,500-package window shows that the answer introduced a new attack surface at the same time it closed the old one.

This is the pattern I keep seeing in [the CI/CD layer, where agents need budgets, gates and a kill switch](https://debugly.dev/agents-in-ci-budgets-kill-switches/). The new authentication method is better than the old one, and the migration to it is the window of vulnerability. Long-lived tokens are the disease, OIDC-based trust is the cure, and the cure has its own incubation period. The 48-hour window is the fix for the incubation period, and the `issue_comment` event restriction is the fix for the event-source confusion that let attackers trigger publishes from contexts that should never have had that power.

The bigger picture is that npm's supply chain defence model shifted from credential secrecy to identity verification, and the identity verification had a gap the size of a forgotten configuration. The lesson for every registry and every package manager that follows the same model is that the migration from old auth to new auth is itself a security-critical process, and the gap between configuring the new method and using it is a window that needs to be measured and closed by default, not left to the maintainer to remember.

## What I now do

Every npm package I maintain or depend on gets the same check: if it uses trusted publishing, confirm the configuration is validated by a recent publish, not just by the existence of the configuration. The 48-hour window makes the check simple: a configuration that was created more than 48 hours ago and has never published is a configuration that has expired, and a configuration that expired and was re-created is a configuration that needs scrutiny.

For CI/CD pipelines that publish to npm, the operational change is small. Make the first publish a deliberate step in the migration, not something that happens at the next release. The window between configuring trusted publishing and using it is the window the attacker needs, and a one-line publish of a patch version closes it. Then check that your workflows do not run on `issue_comment` events with publish permissions, because that door is now locked from the registry side and you should lock it from the workflow side as well.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)