Liran Tal of Snyk published a really useful Github repo called “Awesome npm security best practices”. In this repo, Liran discusses structural constraints within the CLI toolchain, highlighting many practical, architectural shifts away from npm’s historically permissive, "trust-by-default" model. In the wake of attacks like Shai-Hulud and the Sapphire Sleet Axios compromise, security cannot rely on developer discipline or retroactive registry-side malware yanking. The toolchain itself must be engineered to fail-secure.
In this short blog post, we’ll highlight 3 distinct, highly actionable decisions that should become the new norm as far as interacting with software packages on JavaScript’s npm registry:
1. Stop running unvetted code automatically
Github reference
This should seem obvious from a security standpoint, but historically, npm has treated lifecycle scripts (preinstall, postinstall) as first-class, unrestricted citizens. Attackers are able to exploit this by adding sleeper dependencies that were never even imported by the application runtime code. In these scenarios, the sole purpose is to trigger an install-time script that’s able to drop a Remote Access Trojan (RAT).
The CLI must invert its defaults. Instead of requiring developers to pass --ignore-scripts or manually configure ignore-scripts=true in .npmrc, the toolchain should block all lifecycle scripts out-of-the-box. The npm registry should adopt a mechanism similar to pnpm’s strictDepBuilds or allowBuilds map. If a dependency attempts to execute a script that isn't explicitly greenlit in a locked configuration file, the CLI should throw a hard error and block the installation, forcing an explicit audit rather than permitting a silent compromise.
2. Close any backdoor registry loopholes
Github reference
A major structural loophole is the flexibility JavaScript package managers offer in resolving dependencies from unconventional sources, such as raw Git URLs or direct tarballs. These exotic sources completely sidestep registry-side protections - meaning no malware scanning, no OIDC trusted publisher verification, and no provenance cryptographic auditing. To add more fuel to the fire, an attacker could easily commit a malicious .npmrc inside a git dependency that silently re-enables lifecycle scripts on the victim’s machine.
The toolchain really must enforce strict protocol alignment here. Native CLI defaults should mirror npm's allow-git=none or pnpm's blockExoticSubdeps=true. Transitive dependencies must be structurally banned from pulling code outside of the primary secure registry. The CLI should also enforce that only explicitly audited, direct dependencies declared in the root package.json can utilise non-registry sources, effectively securing the wider dependency tree.
3. Introduce a cool-off period for new updates
Github reference
Most supply chain compromises have a highly volatile, but brief, day-or-so golden window where the malware propagates before security researchers or automated scanners flag and remove the package (the Shai-Hulud waves were typically mitigated within 12 hours, and the Axios compromise was also caught within hours). Permissive semver ranges mean automated CI/CD pipelines blindly pull down these poisoned packages minutes after publication.
If you don’t have an existing artifact management layer, like Cloudsmith or Nexus, we would recommend developers introduce a simple cooldown buffer into the dependency resolution engine itself. While features like min-release-age have started rolling out across package managers (such as in npm versions 11.10.0 and higher), the structural shift requires making a relative age gate (for example min-release-age=3d) an opt-out standard rather than an opt-in luxury. By natively refusing to resolve any direct or transitive package version that has not crossed a stability threshold, the CLI acts as a temporal circuit breaker, completely neutralising the smash-and-grab window of rapid worm propagation.
The software supply chain is broken (but we can fix it)
As discussed, there are practical solutions to many of the existing pain points in the npm ecosystem. To recap, npm shouldn't run setup scripts automatically. Turn them off unless they are explicitly trusted. Stop dependencies from pulling unverified code from raw Git links or external URLs that skip standard security scans. Try to avoid automatically downloading brand-new updates the second they publish (unless you absolutely have a strong reason for doing so). Wait a day or two to let the security community catch any hidden malware. If you have followed the above 3 secure-by-default approaches, you can confidently regain trust in open source software, and securely prevent the next wave of supply chain attacks.