Hint: This post is also available in German 🇩🇪.
2026 finds itself even more heavily overshadowed by far-reaching supply chain attacks (SCAs) than the year before. To put it bluntly, not a day goes by without a new report surfacing in software development circles about yet another infected software package capable of compromising one’s own solutions and infrastructure. An SCA, essentially being targeted by malicious actors through third-party dependencies with little to no direct fault of your own, was, just a few years ago, considered a rather theoretical and marginal phenomenon. Today, however, the issue has evolved into an omnipresent and serious threat in the software industry.
This trend is, for example, highlighted in a report published by Sonatype (Figure 1). The graph illustrates the growth of malicious software packages discovered by Sonatype – from the start of record-keeping in 2019 through 2025. 2025 ended with over 1.2 million detected packages – each one representing a separate opportunity to unintentionally infect a victim’s system.
Figure 1: Rise of Malicious Packages (2019 – 2025)
Source: Sonatype (2025)
The nearly exponential increase in such infected packages is particularly concerning. The npm ecosystem accounts for the largest share of this. In 2025 alone, 99% of the packages detected by Sonatype were found on npm. Combined with the large number of such packages installed in an average project in a web or Node.js environment, this raises the question of how to best mitigate the immediate risk of an SCA. While it goes without saying that the risk can never be completely eliminated as long as external dependencies are used, even simple measures and practices can go a long way in reducing the chances of such an incident.
SCAs – A Classification
Essentially, an SCA pursues a single goal: executing malware on one or more target systems to achieve arbitrary objectives, ranging from installing crypto miners to stealing confidential data. What distinguishes SCAs from other types of attacks is the method used to infiltrate the malware. Rather than infiltrating a target system directly, say, through a known vulnerability, SCAs typically take an indirect route by infecting a system's dependencies. This is precisely what makes SCAs so dangerous: dependencies you have trusted for years suddenly turn into vehicles for malicious code, attacking your own system and potentially its users.
There are many ways in which a package can become "Patient Zero" – that is, initially infected and the source of an SCA. Back in 2020, well before the recent surge discussed here, Ohm, Plate, Sykosch, & Meier (2020) published an analysis of open-source SCAs that had already occurred at the time. Figure 2 illustrates the attack paths classified by the authors, ranging from the creation of malicious packages to the compromise of an existing package. Even today, nearly six years after the paper’s publication, the methods depicted remain accurate descriptions of how an SCA can infect target systems.
Figure 2: Potential Attack Vectors for Infecting Packages
Source: Ohm, Plate, Sykosch, & Meier (2020)
For example, back in 2018, the topic of SCAs first gained widespread attention through the "event-stream" attack. In short, the popular npm package event-stream was modified via a malicious pull request ("Pull Request (as contributor)") to add a dependency on another specifically crafted, malicious package named flatmap-stream. The attack specifically targeted the Copay Bitcoin wallet, illustrating early on how quickly malware can be smuggled into a target system via an SCA. Toward the end of 2025, the name "Shai-Hulud" became a collective term and blueprint for SCAs capable of self-propagating after infecting a package, thereby infecting a chain of additional packages. With up to 500 infected packages in just a few days, high-profile victims such as @tanstack/router or Mistral, and recurring waves of attacks in various forms and by different actors, Shai-Hulud is a far more complex and advanced version of an SCA compared to event-stream. In the case of TanStack, however, this wave also began, for example, via an attack path shown in Figure 2 – specifically through the compromise of a build system and subsequently through scripts executed automatically upon the installation of a compromised package.
In general, an analysis of past SCAs shows that understanding how other dependencies and systems have been compromised in the past can help protect oneself from an SCA. The following section presents 10 specific methods that any developer working in web development can use to reduce the risk of an SCA – both for themselves and for their team.
10 Ways to Prevent Attacks in the npm Ecosystem
ℹ️ Note
All of the examples and configurations mentioned here rely on npm as the underlying package manager, given that it is typically the most widely used among the available package management options. That said, alternatives like Yarn or pnpm generally support the same or equivalent features and in some cases even more granular controls.
1 Disable Lifecycle Scripts
Lifecycle scripts, also known as install scripts, are scripts that are automatically executed by npm when interacting with a package at specific times, such as during the package’s installation. Legitimate packages use these for perfectly valid purposes (our own piral-cli, for instance, relies on the postinstall script to automatically generate useful code files). However, these scripts become a major liability when malicious packages enter the picture, as any package can execute any code via lifecycle scripts. This opens up a massive attack vector: every time an infected package is installed, it can execute malicious code directly on the target system.
Even though lifecycle scripts can be useful, the risks they pose usually outweigh the potential benefits. In most cases, it makes sense to disable these scripts altogether, preventing the execution of malicious code at its root. npm enables this through the ignore-scripts configuration setting, which can be configured locally for an individual user/machine or set for the entire development team in the following ways:
Figure 3: Deactivating Lifecycle Scripts via CLI
Figure 4: Deactivating Lifecycle Scripts via .npmrc
ℹ️ Note
It is often a good idea to configure these (and future) settings in both locations, i.e., both for the current machine and via a .npmrc file for the entire project. This protects not only your own development environment but also those of team members or, for example, automated pipelines.
ℹ️ Note
Starting with npm version 12, lifecycle scripts were disabled by default. Manually disabling them may therefore no longer be necessary. However, project-wide configurations via .npmrc may still be useful, at least temporarily, as long as, for example, not all team members or pipelines are using npm version 12.
2 Version Pinning
When referencing specific dependency versions, npm offers several version resolution strategies. For any given dependency, you can:
- Request an exact version (e.g., 1.2.3).
- Request the latest version within a major version (e.g., the latest version starting with 1).
- Request the latest version within a minor version (e.g., the latest version starting with 1.2).
- Always request the latest version.
To support this, special syntax is used within a package.json file:
Figure 5: npm Syntax Versioning Syntax
The default setting when installing a new package is Option 2, that is, a versioning strategy in which the specific version of the package to be installed is not necessarily known in advance! This "dynamic" versioning poses a risk in the context of an SCA. If a compromised version of a package is published within a dynamic version range, that package may be installed automatically.
To guard against these scenarios, one can rely on three key practices:
- Actively "pinning" installed dependencies.
- Using lockfiles (package-lock.json).
- Running "clean installs" (npm ci command) to install packages.
Pinning dependencies refers to the above Option 1: specifying an exact package version. This stabilizes a project's list of first-level dependencies until the next manual version update. You can pin dependencies simply by removing the special syntax from a package.json file (see Figure 6).
Figure 6: Pinning Versions
Important: Pinning versions only stabilizes direct dependencies. Transitive dependencies, i.e., packages referenced by other packages, can still be resolved dynamically, and thus also become a potential entry point for an SCA.
To address this issue, one can leverage points (2) and (3). Lockfiles define the exact dependency tree that is initially installed whenever a package version changes. Unlike npm i, the npm ci command installs strictly the exact versions specified in the package-lock.json file. That's why package-lock.json files should always be shared across a project and team – for instance, by committing them to a code repository.
3 min-release-age
Today, many SCAs are swiftly caught by automated, often AI-powered, analysis tools and providers, and immediately removed from npm once detected. As a result, compromised packages are usually only available for installation for a brief window of time.
This is where the min-release-age configuration option comes into play. This option allows you to set a minimum age that a package must have reached to be installed by npm. A timeframe of, say, three to seven days could already help exclude a potential list of compromised packages from installation. Like lifecycle scripts, this option can be set at the user, machine, or project level:
Figure 7: Configuring min-release-age via CLI
Figure 8: Configuring min-release-age via .npmrc
4 Intentional Updates
New dependency versions are published almost daily in the npm ecosystem, and projects often adopt them right away or very quickly. However, for the same reasons as with min-release-age, it may also make sense to perform version updates only deliberately and, if possible, even avoid them or delay them long enough to create a time buffer against SCAs. Not every update brings real value to a project. For example, if an update merely fixes a feature that a project doesn't even use, updating might pose more risks than benefits given the threat of SCAs. Taking a deliberate approach to new npm package versions can go a long way toward preventing an SCA in your own environment. Nonetheless it is important to ensure that updates to new versions aren’t delayed for too long. Important security updates or new major versions should, whenever possible, be integrated promptly to prevent future vulnerabilities and long, painful upgrade cycles down the line. Striking the right balance for a specific package ultimately depends on the situation.
5 Are Dependencies Necesary?
In the npm ecosystem in particular, there are a multitude of (very small) packages that are frequently used by projects. A curious example made headlines a few years ago when the maintainer responsible for the left-pad package removed it from npm. left-pad is an npm package containing a single utility function just a few lines long. The removal caused significant disruptions in many projects, including large and well-known ones. The heated debate at the time about why projects use such packages in the first place and why such small helper functions aren’t developed and managed directly at the project level is also interesting from the perspective of preventing SCAs. It is often possible to simply remove at least a small portion of dependencies – or entirely replace them with self-managed code – thereby mitigating the risk of an SCA. This is particularly true in today’s age of AI, which has drastically reduced the effort required to write and maintain such helper functions. But it’s not just small packages that are worth examining: The JavaScript ecosystem is constantly evolving and, in some cases, even allows for the removal of larger packages. One example of this is the upcoming Temporal API – a native replacement for larger in scope packages such as moment or dayjs. Regularly reviewing and questioning your dependencies is well worth the effort!
6 Using Scopes and Registries
Companies often rely on private, externally inaccessible npm registries and packages. Yet even here, common entry points for SCAs exist. For instance, through a dependency confusion attack, an attacker can still manage to infect systems despite the use of internal registries and packages.
In general, there are two easy-to-implement measures that can help prevent such an attack:
- Using Scopes: npm packages can begin with a scope (a prefix in the format @scope/PACKAGE_NAME). Scopes can be linked to a specific npm registry in a .npmrc file (see Figure 9). This configuration can be used to force npm to download packages exclusively from the desired private registry.
- Registering Scopes on npmjs.com: Private npm registries often mirror the public npm registry and download packages from there that cannot be found internally. If custom package scopes are used internally and that scope is not registered in the public npm registry, a potential attacker could register that scope, publish a compromised package with a high version number under that scope, and thereby cause the internal registry to mirror this malicious package. To close this gap, scopes used internally should also be registered in the public npm registry whenever possible. Under registered scopes, no other parties outside the owner’s group can publish packages.
Figure 9: Configuring Scopes
7 Auditing
Auditing refers to the automatic scanning of dependencies for vulnerabilities. Automated and regular auditing, for example, as part of a project’s build pipeline, can help react quickly to the use of a compromised package in a project once it becomes known. Auditing generally focuses on mitigating an SCA, that is, detecting malicious packages already in use within a project.
There are many providers and tools for automated scans, both free and commercial. Already integrated into npm is the npm audit command, which finds and lists vulnerable dependencies using the GitHub Advisory database. When run as part of a CI/CD system, npm audit can stop pipelines and builds immediately if critical dependencies are detected. As a minimum, the end-to-end integration of npm audit into all build pipelines – where supported – makes sense in the vast majority of cases.
8 Sandboxing und Isolation
Sandboxing and isolation also fall under the umbrella of mitigation. As a developer, you often work on multiple projects – or at least have files from different projects stored on the same system. This often means that alongside the current project, secrets and credentials belonging to other projects are also accessible on the machine. Without proper isolation between projects, a compromise of a single project via an SCA can lead to the theft of sensitive data from other projects as well – not to mention any general secrets found on the system.
To counteract this, it can make sense to develop projects in isolation from one another. In an isolated environment, an SCA can only access the data specifically made available to that particular project, giving you a much more manageable baseline when responding to such an attack. There are many ways to achieve isolation, such as developing within project-specific VMs, using Dev Containers, or leveraging platforms like GitHub Codespaces.
9 AI Agents
AI agents have now established themselves as a standard tool among developers. Yet, or perhaps precisely for that reason, they present an attack vector in the context of SCAs. Sonatype reports, for example, that AI agents can demonstrably be tricked into installing a compromised dependency. Furthermore, reports highlight how attackers are increasingly tailoring their attack paths toward AI agents, using methods such as typosquatting in package names.
As a developer, you should at least ask yourself to what extent you want to trust an installed AI agent with managing dependencies. From completely revoking dependency management tasks to granting full permission, for instance in combination with strong project isolation, both options can be valid depending on the situation. At the very least you should be aware that AI agents, and by extension the user behind them, can fall victim to an SCA.
10 Maintainer – 2FA & Trusted/Staged Publishing
This section is intended for maintainers of npm packages. npm itself has responded to the rapidly increasing number of infected packages and has increased the number of available options that can potentially prevent a compromised package from being published under one’s own name:
- Two-factor Authentication (2FA): 2FA is practically mandatory for package publishing – setting up a second factor protects against account takeover, safeguarding the ability to upload new package versions.
- Trusted Publishing: npm supports the Trusted Publishing standard. This allows you to publish a package directly from a build pipeline without any manual management of long-lived authentication tokens—instead, short-lived tokens are issued automatically. In addition to automatic usability improvements (tokens can no longer accidentally expire and cause pipelines to fail), the tokens used by Trusted Publishing are harder to leak and, due to their short lifespan, much harder for malicious actors to exploit overall.
- Staged Publishing: A relatively new option is the ability to stage packages on npm prior to final publication. Unlike direct publishing, a package staged by a build pipeline, for instance, must be explicitly approved and verified via 2FA by a maintainer before it appears as a new version and can be installed.
Combined, these three options offer a strong shield for both preventing and mitigating potential SCAs. On their own, these measures can be circumvented. For instance, Trusted Publishing alone did not prevent the SCA on TanStack. In hindsight, a combination of Trusted and Staged Publishing might have led to a different outcome here.
Conclusion
Is it possible to completely prevent a potential SCA in one’s own environment using the methods described here? This question must be answered with a resounding "no". While the methods outlined can, at best, block an SCA or at least mitigate its impact, even they do not offer 100% protection. The complex attack on TanStack highlights how threat actors are already developing and deploying sophisticated techniques to hide malware deep within the forest of software dependencies. As paranoid as it might sound: in modern software development, you should no longer ask if you will be hit by an SCA, but when – and ideally prepare appropriate prevention and response measures accordingly.
Most of the methods mentioned above focus on prevention, and although they are tailored to the npm ecosystem in this article, they may also be applicatable to other languages and environments like Python/Pip or C#/NuGet. In addition to prevention, however, it also makes sense to plan ahead for the worst-case scenario and, ideally, establish an incident response process that can be executed if an SCA does succeed. Ultimately, vigilance and actively monitoring your own dependencies are the best protection. Taking SCAs seriously as a threat and acting on that reality will leave you far better protected in the long run.
References
- Ohm, M., Plate, H., Sykosch, A., & Meier, M. (2020). Backstabber’s Knife Collection: A Review of Open Source Software Supply Chain Attacks. International Conference on Detection of Intrusions and Malware, and Vulnerability Assessment (S. 23-43). Springer, Cham.
- Sonatype. (2025). Open Source Malware at the Gate - The Evolving Software Supply Chain Attack Surface.









Top comments (0)