Safeguard
Open Source

npm Hack: How Supply Chain Attacks Work and How to Stay Safe

An npm hack rarely means npm itself was breached. It usually means a maintainer account was phished or a package was hijacked. Here is how these attacks unfold and how to defend your builds.

Safeguard Research Team
Research
6 min read

An npm hack almost never means the npm registry itself was breached, it means an attacker took over a popular package, usually by phishing a maintainer's account or poisoning their publishing pipeline, and pushed malicious code that your build then downloads automatically. Because a single compromised package can sit deep in thousands of dependency trees, one npm hack can ripple across billions of weekly downloads. Understanding the mechanics is the first step to not becoming collateral.

Why npm is such a rich target

The JavaScript ecosystem is built on small, deeply nested dependencies. A typical front-end app pulls in hundreds of transitive packages, most of which the developer never chose directly and could not name. That structure is efficient for reuse and catastrophic for trust: the security of your application is the security of the least careful maintainer anywhere in that tree.

Attackers understand this leverage. Compromising one widely depended-on utility, like a logging or terminal-coloring library, instantly reaches every project that transitively depends on it. That is why so many of the largest incidents have targeted "boring" foundational packages rather than flashy ones.

How a modern npm hack unfolds

The attacks have a recognizable shape, even as the details evolve.

Account takeover through phishing. The September 2025 compromise of debug, chalk, and 16 other packages, which together see billions of weekly downloads, began with a phishing email. A maintainer received a convincing two-factor-reset message from a lookalike domain, npmjs.help, handed over credentials, and the attacker published malicious versions that targeted cryptocurrency wallet interactions in the browser.

Pipeline and token compromise. Not every attack needs the maintainer's password. The Nx-related "S1ngularity" incident in August 2025 abused publishing tokens to push malicious package versions and exfiltrate secrets. The TanStack compromise in May 2026 chained pull-request-target abuse, GitHub Actions cache poisoning, and OIDC token extraction to publish dozens of malicious versions across many packages without ever logging in as a human.

Self-propagating worms. The Shai-Hulud campaign went a step further, running a multi-stage payload that stole credentials and, when it found GitHub access, published a repository of exfiltrated secrets and poisoned further npm packages to spread on its own.

The scale is sobering. Sonatype's 2026 supply chain report counted more than 454,000 new malicious open-source packages in 2025 alone, pushing the cumulative total past 1.2 million.

What the malicious payloads actually do

Once installed, the hostile code tends to do one of a few things:

  • Steal credentials and secrets. Environment variables, .npmrc tokens, cloud credentials, and SSH keys are the usual prizes, often exfiltrated to an attacker-controlled endpoint during postinstall.
  • Hijack crypto transactions. Browser-facing payloads rewrite wallet addresses at transaction time so funds route to the attacker.
  • Establish persistence. Some payloads plant backdoors or additional packages to survive cleanup.

Crucially, much of this fires during install, not at runtime, through lifecycle scripts. That means npm install in CI can be enough to compromise a build agent before a single line of your own code runs.

Defending your builds

You cannot audit every maintainer, but you can shrink your exposure and detect compromise fast.

Pin and lock everything. Commit your package-lock.json and install with the lockfile honored, not silently updated:

# CI should use ci, which fails if lockfile and package.json disagree
npm ci

Disable install scripts where you can. Many payloads rely on lifecycle hooks. For CI and for packages that do not genuinely need them:

npm ci --ignore-scripts

Then explicitly rebuild only the native modules that truly require a build step.

Delay adoption of brand-new versions. A large share of malicious versions are caught within hours to days. Configuring a minimum release age before a version is eligible for install blunts the fast-moving campaigns.

Enforce 2FA and use granular tokens. If you publish packages, require two-factor authentication and use automation tokens scoped narrowly, rotated often, and never stored in plaintext CI logs.

Continuously scan your dependency tree. Manual review does not scale to hundreds of transitive packages. Software composition analysis watches for known-malicious versions and newly disclosed advisories across the whole tree, including transitive depth. An SCA tool such as Safeguard can flag a compromised transitive dependency and the exact path that pulled it in, which is what you need to respond in minutes rather than days. Our software composition analysis product page covers this in more detail.

What to do if you are hit

Speed matters. If a package in your tree is flagged as compromised:

  1. Identify the exact malicious version and every path that resolves to it.
  2. Pin to the last known-good version or remove the dependency, and run npm ci to rebuild the lockfile cleanly.
  3. Rotate every credential that the affected build agent or developer machine could have touched, treating exfiltration as assumed rather than hypothetical.
  4. Review CI logs and outbound network activity for exfiltration attempts during the exposure window.
  5. Purge caches, including CI dependency caches, so the bad version cannot silently reappear.

Assume-breach is the right posture. If a credential-stealing payload ran in your pipeline, the incident is not "did we remove the package" but "which secrets do we now need to consider burned."

FAQ

Was the npm registry itself hacked?

In nearly every major incident, no. The registry served malicious versions that attackers published after taking over a maintainer account or publishing pipeline. The distribution channel was abused, not breached at its core.

How do malicious npm packages usually run their code?

Frequently through lifecycle scripts such as postinstall, which execute during npm install. That is why running installs with --ignore-scripts in CI is a strong mitigation.

Does a lockfile protect me?

A committed lockfile plus npm ci prevents silent version drift, so you install exactly what you reviewed. It does not help if the version you pinned is itself compromised, which is why continuous scanning matters too.

How fast are malicious versions detected?

Many are caught within hours to a few days, which is why delaying adoption of brand-new releases and scanning continuously together close most of the window.

Never miss an update

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