Safeguard
Open Source Security

npm's shift from implicit to explicit trust: what changed...

npm quietly rebuilt its trust model in 2025 after the chalk/debug hijack and the Shai-Hulud worm. Here's what changed, why JFrog's curation model isn't enough, and how Safeguard closes the gap.

Aman Khan
AppSec Engineer
7 min read

On September 8, 2025, a phishing email cost the JavaScript ecosystem its illusion of safety. A maintainer known as Qix had his npm account compromised, and attackers pushed malicious versions of chalk, debug, ansi-styles, and roughly a dozen other packages that together see close to 2 billion downloads a week. The payload was a browser-based crypto-clipper, live for barely two hours before the community caught it — but the incident, followed weeks later by the self-propagating "Shai-Hulud" worm that infected more than 500 packages by stealing and reusing publish tokens, forced npm to admit something it had avoided for nearly two decades: publishing a package should never have been as easy as knowing a password. This post walks through what npm's trust model used to assume, what concretely changed through 2025, how that compares to registry-side approaches like JFrog's, and what it means for teams shipping JavaScript today.

What did npm's trust model actually assume before 2025?

It assumed that a valid credential meant a valid human. For most of npm's history, publishing a package required nothing more than an npm account and, for a subset of maintainers, a TOTP code. Access tokens — the strings CI pipelines and laptops used to push releases — were "classic" tokens: they never expired unless a maintainer manually revoked them, they carried full account privileges by default, and they worked identically whether they were typed into a terminal by a person or pasted into a GitHub Actions secret. npm only began requiring two-factor authentication for the top 100 packages by download count in February 2022, and even then TOTP (a code from an authenticator app) satisfied the requirement — a method still vulnerable to phishing and SIM-swap attacks. Everyone else, meaning the overwhelming majority of the 3+ million packages on the registry, published under implicit trust: if you had the token, you were assumed to be the maintainer.

What actually triggered the shift to explicit trust?

Two attacks eight days apart in September 2025 turned a theoretical risk into a lived incident for the entire ecosystem. The chalk/debug compromise on September 8 showed that even ubiquitous, heavily-depended-upon packages — chalk alone is a transitive dependency of hundreds of thousands of projects — could be weaponized in minutes once a single maintainer's credentials were phished. Then Shai-Hulud, discovered spreading in mid-September, went further: it used stolen npm tokens to automatically publish trojanized versions of any other package the compromised maintainer had access to, scanned infected machines with TruffleHog for additional secrets, and exfiltrated them to public GitHub repositories, all without human involvement at any step. It was the first genuinely self-propagating worm the npm ecosystem had seen. Both incidents shared the same root cause: a static, long-lived credential was treated as sufficient proof that a publish action was legitimate. That assumption is what npm spent the rest of 2025 dismantling.

What concrete changes has npm made since then?

npm replaced "does this token exist" with "can this specific action be justified right now," across three fronts. First, publishing from a local machine now requires a fresh two-factor prompt at the moment of publish, not just at login — closing the window where a stolen session token alone could ship a release. Second, npm capped the lifetime of granular access tokens and moved to deprecate classic, non-expiring tokens outright, with automation tokens defaulting to shorter validity windows that must be actively renewed. Third, and most significantly, npm rolled out trusted publishing in July 2025: an OIDC-based flow, modeled directly on PyPI's 2023 rollout of the same concept, that lets a CI system like GitHub Actions exchange a short-lived, workflow-scoped identity token for a one-time npm publish credential — eliminating the need to store any long-lived npm secret at all. Combined with npm provenance attestations (GA since npm 9.5.0 in 2023), which cryptographically bind a published package back to the exact source commit and build workflow that produced it via Sigstore, the registry now lets consumers verify not just who published a package but under what conditions it was built.

How does this compare to how JFrog approaches trust?

JFrog's model manages trust primarily at the point of consumption rather than the point of publication. JFrog Xray and JFrog Curation sit in front of package resolution, scanning and blocking known-malicious or policy-violating packages before they reach a build — a proxy-and-gate architecture that has genuinely helped customers catch compromised versions of chalk and the Shai-Hulud-infected packages before they were pulled into a pipeline. But that architecture is reactive by construction: it depends on a package already being flagged as malicious in a threat feed, and it says nothing about whether the maintainer who published version 5.4.1 this morning is the same person — or the same automated, attested workflow — who published 5.4.0 last month. npm's 2025 changes attack the problem from the opposite end: they raise the cost of forging a legitimate-looking publish event in the first place. The two approaches are not competing so much as answering different questions. JFrog asks "is this artifact known-bad?" npm's new controls ask "was this artifact produced through a process we can actually vouch for?" Organizations relying on curation alone remain exposed to the gap between those two questions — a novel, first-hour compromise that hasn't hit a threat feed yet looks identical to a routine release.

What does this mean for engineering teams shipping JavaScript today?

It means dependency risk has split into two distinct problems that need two distinct controls. The first is upstream identity risk: was this release actually authorized by a trusted, attested process? Provenance attestations and trusted publishing give you the raw signal, but npm doesn't enforce that your organization actually checks it — npm install will happily pull an unattested package with zero warnings today. The second is runtime/behavioral risk: does this package, regardless of who published it, do anything a dependency shouldn't — reach out to an unexpected domain, read environment variables, touch ~/.ssh? Shai-Hulud's TruffleHog-based secret scanning and the chalk crypto-clipper's DOM-injection behavior were both detectable at the behavioral level well before either had a CVE or a threat-feed entry. Teams that only inherited npm's new publish-time controls, and teams that only bolted on a consumption-time scanner, both still have a hole: the first can't catch a compromised-but-properly-attested account, and the second can't catch a brand-new attack pattern it hasn't seen described yet.

How Safeguard Helps

Safeguard is built for exactly the gap between "npm says this was attested" and "your pipeline actually verified it before installing." Rather than waiting on a threat-intelligence feed to catch up to the next chalk-style compromise, Safeguard inspects dependency changes as they land — flagging packages published without provenance, publishers who suddenly ship from a new, unattested workflow, and version bumps that introduce install-time or runtime behavior (network calls, filesystem access, obfuscated payloads) inconsistent with a package's history, the same signature Shai-Hulud and the chalk clipper both carried. That behavioral layer sits on top of, not instead of, the identity signals npm now provides: Safeguard checks provenance and trusted-publishing attestations automatically and can enforce policy — block, quarantine, alert — the moment a dependency fails to meet your bar, whether the failure is "unattested publisher" or "obfuscated postinstall script." For teams already running JFrog for artifact management and curation, Safeguard adds the maintainer-identity and behavioral-anomaly layer that a catalog-based gate can't provide on its own, closing the window between a credential being stolen and a threat feed catching up. The npm ecosystem just spent a year rebuilding trust from the ground up; Safeguard makes sure your builds actually benefit from it, instead of quietly ignoring the attestations that are already sitting in your dependency tree.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.