In the last few months, we’ve seen a steady drumbeat of malware campaigns targeting npm: the Shai-Hulud worm this week, the compromises of popular packages like chalk and debug last week, and the s1ngularity NX attack earlier this summer. These incidents all share the same traits: they target developers directly, attempt to steal secrets or credentials, and aim to move laterally into larger systems.
The impact can be large. But the attacks themselves tend to be short-lived—researchers and registries usually discover and revoke the malicious versions within a day or two. That creates a paradox: organizations must defend against a fast-moving, high-impact threat that often burns out within 24–48 hours.
Defending against malware attacks isn’t just about reacting faster. It requires teams to implement proactive protections that limit the likelihood of malicious packages entering your environment in the first place. Two of the most practical ways to do this are strengthening how you manage versions and slowing down adoption of unvetted code. Pinning and lockfiles give you determinism against surprise updates, while cooldown periods buy time for the ecosystem to uncover malicious releases before they hit your environment.
The difference between pinning and lockfiles
Dependency pinning means locking a dependency to an exact version rather than a range. In npm, "express": "^4.18.0" allows upgrades within 4.18.x, while "express": "4.18.2" pins it to that exact version. Typically, you pin direct dependencies in your package.json, but this does not guarantee that transitive dependencies (those required by your dependencies) are pinned—because the packages you import may declare ranges instead of fixed versions.
Lockfiles (package-lock.json and npm-shrinkwrap.json) address this gap by recording the entire resolved dependency tree, including transitive dependencies, along with URLs and integrity hashes. For example, even if package.json has "express": "^4.18.0", the lockfile pins the actual resolved version such as "express": "4.18.2". package-lock.json is intended for reproducible installs in your repo, while npm-shrinkwrap.json can be published so consumers also get the exact pinned tree.
Lockfiles for different package managers in the JavaScript ecosystem (e.g npm’s package-lock.json J, Yarn’s yarn.lock, pnpm’s pnpm-lock.yaml) serve the same purpose but differ in format and determinism guarantees. Edge cases include the fact that package-lock.json is ignored when publishing libraries, so consumers may resolve different versions; mismatched npm versions leading to lockfile churn; optional or peer dependencies that install differently across platforms; and lockfile drift or merge conflicts. Pinning improves stability, but without lockfiles and updates, your builds may still vary and malware can still sneak in via your transitive dependencies.
The case for cooldown periods
Most malicious package releases are discovered within the first 24–48 hours of being published, often by automated scanners, security researchers, or the package maintainers themselves. The projects that get compromised are typically those that do not have pinned dependencies or lock files and thus get whatever npm gives them when the package is built.
As a result one of the most effective defenses against short-lived malware campaigns is simply waiting. You can implement a “cooldown” period of 24-48 hours before you allow any new versions of packages to be used in your environment. This gives registries, vendors, researchers, and the community enough time to catch and flag malicious packages before they reach your codebase.
Npm doesn't afford you the ability to do this easily, while pnmp very recently does. Organizations can build versions of this themselves using a private registry, but that involves infrastructure setup, auth and permission management, caching of public packages, and configuration for developers and pipelines. It’s difficult because it introduces ongoing security, reliability, and maintenance responsibilities that the public registry normally handles for you.
How to use Endor Labs to enforce proactive controls
At Endor Labs, we built our policy engine to help you create and enforce custom policies as code using the Open Policy Agent (OPA) and the Rego language. Since our inception we’ve offered an out-of-the-box policy to flag unpinned direct dependencies. This policy creates findings for dependencies that could be susceptible to malware attacks.
We’ve now added a new out-of-the-box policy to detect recently released dependencies (in both your direct and transitive dependencies). By default, it enforces a 48-hour cooldown, but you can configure the window to fit your risk tolerance.

You can pair this with an action policy to warn developers or block commits that attempt to introduce dependencies released in the last 48 hours into your environment. You can tailor it for specific ecosystems, like npm, and decide where to enforce it, like in pull requests:

Flexible Policies, Powered by Rego
Because this is all built on Rego, you can write custom policies that leverage the metadata in Endor Labs. In addition to scanning packages for malware, we evaluate every open source package using 150+ health checks spanning maintainer activity, repository security, license compliance, and more.
Here’s how the same cooldown logic looks when expressed as a Rego policy:
.png)
This flexibility means you’re never locked into our defaults—you can define custom requirements, dependency rules, or block conditions that reflect your exact environment and workflows.
Takeaway
Malware moves fast, but it doesn’t last long. The best way to reduce your exposure is to slow down with pinned dependencies, cooldown windows, and enforceable policy-as-code. With Endor Labs’ policies and malicious package detection, you can protect developers and keep malware out of your software supply chain.
Detect and block malware



What's next?
When you're ready to take the next step in securing your software supply chain, here are 3 ways Endor Labs can help: