Code dependency graph representing supply chain security

Software supply chain attacks aren't new, but 2025 produced a concentrated set of campaigns that targeted npm specifically — and the scope was larger than most teams realized at the time. If you were running JavaScript in CI/CD pipelines without paying close attention to your dependencies, there's a reasonable chance your environment was scoped by at least one of these campaigns even if you never got a security alert.

In November 2025, researchers from Wiz, Aikido, and other firms detailed what they called a "Shai-Hulud 2.0" campaign: a wave of trojanized npm packages that exfiltrated developer and CI/CD credentials from environments using popular libraries. Separately, GitLab's vulnerability research team documented another widespread attack that harvested credentials for GitHub, npm, and major cloud providers, propagating by infecting additional packages owned by compromised victims.

How these attacks actually work

The basic mechanics are worth understanding, because "supply chain attack" gets used loosely enough that people sometimes picture something exotic. In the npm campaigns, the most common pattern was either: (1) publishing a package with a name close to a popular one — typosquatting — counting on developers to mistype an install command; or (2) compromising the npm account of an existing maintainer and pushing a malicious version of a legitimate package.

The malicious code typically does something like exfiltrating environment variables when the package is installed or first imported. In a CI/CD environment, those environment variables often include cloud provider credentials, API tokens, and private repository keys. The attacker gets credentials; the developer gets no immediate signal that anything happened.

A study by ReversingLabs noted that while the number of observed malware packages declined slightly in early 2025, the risk shifted toward leaked developer secrets and build-time exposures rather than runtime malware. The target changed from production systems to developer environments, which are often less hardened.

Why the blast radius is bigger than it looks

The reason these attacks can affect thousands of downstream applications from a single compromised package is the depth of typical dependency trees. When you run npm install on a modern JavaScript project, you're not just installing your direct dependencies — you're installing their dependencies, and their dependencies' dependencies. A typical mid-size application can easily have 500–1000 packages in its lockfile, the vast majority of which the developer has never directly chosen or audited.

JFrog and Veracode have both noted that exploding dependency graphs, faster release cycles, and heavy reuse of open source libraries mean a single malicious package can propagate through thousands of downstream applications in days. October 2025 set a new monthly record for supply chain attacks, with open source ecosystems prominently among the targets.

There's also a secondary effect: when attackers compromise a maintainer's account and push a malicious version, they've now infected everyone who pulls that version. If the package in question is something like a commonly used utility library, the reach is substantial.

What the open source community is doing about it

The most meaningful structural response has been around provenance. Sigstore — originally developed by Dan Lorenc while at Google, now governed by the Open Source Security Foundation — provides tooling to verify where a package came from and that it was built from a specific commit. If a malicious version was built outside of the expected CI environment, Sigstore attestation would flag the mismatch. Adoption is growing but still not universal.

Software Bills of Materials (SBOMs) have gotten more attention since the XZ Utils backdoor in 2024, which demonstrated how a long-running, patient attacker could insert a backdoor into a widely-used system library over months. An SBOM gives you an inventory of exactly what's in your dependency tree, which is necessary before you can do meaningful monitoring. The 2025 npm campaigns reinforced this: teams that had no visibility into their transitive dependencies had no way to know they were affected until credentials started appearing in unexpected places.

GitHub added mandatory 2FA requirements for maintainers of popular packages, which addresses the account-compromise vector directly. That's meaningful — a lot of the campaign's success depended on being able to push to legitimate package names via stolen credentials.

What's worth changing in your workflow

Lockfiles are not optional. If you're not committing and respecting a lockfile (package-lock.json or yarn.lock), you're installing whatever the current version is on every build — which means a newly-published malicious version of a dependency will be pulled automatically. A lockfile pins versions so that only intentional updates change what you install.

Tools like npm audit and dedicated dependency scanners (Snyk, Dependabot, Socket.dev) give you ongoing visibility into known vulnerabilities and suspicious package behavior. Socket.dev specifically analyzes package behavior — not just known CVEs — and flags things like newly added network calls in packages that didn't have them before. That kind of behavioral analysis is more likely to catch a fresh supply chain attack than a CVE database that's inherently retrospective.

Least-privilege CI/CD credentials help limit the damage when something does slip through. If your build environment's cloud credentials can only do what the build actually needs — deploying to a specific environment, pushing to a specific registry — a compromised credential is less useful to an attacker than one with broad account access. This is tedious to set up correctly, but it's one of the more reliable mitigations.

The underlying tension here is that open source's strength — easy reuse, fast development — is also what makes this attack surface large. That tension isn't going away. The goal isn't to stop using open source dependencies; it's to be intentional enough about what you're pulling in that you'd notice if something changed unexpectedly.