Insights | 8 West Consulting

Supply Chain Defense for Modern Package Managers

Written by Russ Painter | 2026 - June 8

😟You should be scared

 

Recent NPM supply chain attacks are a reminder that the use of package managers is a real danger. But it's just not avoidable, building without package references is not practical.

This is not just a TypeScript problem. Kotlin, C#, Python, and others all have the same fundamental issue: modern software routinely imports code from hundreds or thousands of strangers during install and build. That should make everyone a bit uncomfortable.

The arrival of AI empowered attackers is magnifying the problem.

 

πŸ•‘ The Attack Window

 

Most modern package attacks are temporary:

 

  • A maintainer account gets compromised.
  • A poisoned package gets published.
  • CI pipelines and developers auto-install it immediately.
  • Bad shit happens.
  • The poisoned package gets detected and removed a few hours later.

 

The attack may only exist for a few hours or days. So one strategy is to try to not be in that early adopter group that's vulnerable. But a balance also has to be found because we don't want to wait too long and miss out on a valid security patch.

 

The Ecosystem Is Finally Improving

 

The industry is now converging on a few sensible ideas:

  • delay adoption of newly published packages
  • restrict dependency sources
  • restrict install-time script execution
  • enforce version lock files

 

❄️ Cooldowns Are One of the Best Defenses

 

The single highest ROI defense is package release cooldowns.

PNPM implemented this early:

 

//// pnpm-workspace.yaml

// 1440 minutes = 1 day

// 4320 minutes = 3 days

 

minimumReleaseAge: 4320

minimumReleaseAgeExclude:

- react

- typescript

 

The package manager refuses to install versions published too recently. You can also exempt trusted packages to keep up with the latest versions of these.

This idea is spreading quickly to other package managers and language ecosystems.

Like anything, this change isn't without it's cost. It can be a maintenance issue when you know a newer valid version is required, but it's still within the cooldown period. If your solution can't wait it out, then you may have to setup an exclusion, but then remember to remove that exclusion later.

 

In the TypeScript world, PNPM Still Has the Strongest Overall Security Model

 

PNPM still has the strongest overall defense-in-depth model right now. Especially since version 11 started enabling some protections by default. And they are actively improving the situation.

 

β˜‘οΈ Block Dependencies From Arbitrary Sources

 

One PNPM feature I particularly like that is included by default in version 11:

 

//// pnpm-workspace.yaml

 

blockExoticSubdeps: true

 

This blocks transitive dependencies from resolving through:

 

  • git URLs
  • tarballs
  • random external sources
  •  

Instead of:

 

"some_dependency": "github:user/project"

 

or:

 

"some_dependency": "https://some-random-site/package.tgz"

 

everything must come from trusted registries.

 

Attackers increasingly hide payloads in unusual dependency paths because:

 

  • they are harder to audit
  • they bypass normal registry visibility
  • they bypass some scanning tools

 

πŸ€– GitHub Dependabot

 

Repositories hosted on GitHub have a feature available called Dependabot which scans your project for outdated dependencies. When found, it will create a pull request suggesting the upgrade. This is cool, but it's another way an un-ripened package may get into your project. It is configurable to specify a cooldown period there as well.

 

πŸ” Version Locking

 

Your package references include the version being relied on. Often this will include version wildcards saying you will accept more recent patch, minor, or major upgrades automatically. This has a couple problems:

 

  • Your CI system can include package versions you haven't tested
  • "It works on my machine" confusion where different developers are running with different dependencies because they ran the update script at different times.

 

You can mitigate this by removing or minimizing those wildcards to prevent the automatic updates. You should also check your package lock-files into source control to ensure everyone is working off of the same version. This doesn't come for free since you now have to invest time in doing those upgrades manually.

But this leads to the next topic:

 

πŸ™Ž Human + AI Verification

 

Don't blindly accept package updates. Go to the package repository and see what changed. Dig into the GitHub source and compare the code changes. You'll often find nice surprises there like new features or improvements you can take advantage of.

AI is good for analyzing the cryptic maze of dependencies, so you can ask it to check for potential version incompatibilities or give you a list of changes.

 

πŸ’€ Install Scripts Are Dangerous

 

The NPM ecosystem normalized install-time code execution years ago and everyone just accepted it. It should never have become normal. The way this works is that when you download a package, some code runs on your machine to set it up, maybe compiling source for your particular operating system. But this is opening the door for anything to be run silently on your machine.

 

A huge number of supply chain attacks rely on these package manager scripts:

 

  • preinstall
  • postinstall
  • prepare

 

The safest option is still:

 

ignore-scripts=true

 

But that is often too aggressive for real projects and could cause some tools to stop working. PNPM has a better option now to allow white-listing of valid scripts that are essential:

 

allowBuilds:

esbuild: true

sharp: true

 

This Applies Beyond TypeScript

 

The implementation details differ, but the same principles are starting to appear almost everywhere. Where the package manager doesn't directly have this support, you can ask your bot to help you out, prompting it to find potential upgrades over a given cooldown age.

 

C# .NET : NuGet

 

  • no native cooldown support currently πŸ™
  • packages.lock.json locks dependency graphs
  • NuGet.config can restrict trusted package feeds
  • package source mapping restricts which packages may come from which registries
  • signed packages verify publisher authenticity

 

Python

 

  • uv supports package cooldowns / minimum package age
  • requirements.txt and uv.lock lock dependency versions
  • pip --require-hashes verifies exact package artifacts
  • private PyPI indexes restrict dependency sources

 

Kotlin : Maven/Gradle

 

  • no native cooldown support currently πŸ™
  • Gradle and Maven support dependency lock files
  • Gradle dependency verification validates package checksums
  • repository allowlists restrict dependency sources
  • internal artifact mirrors are common in enterprise environments to curate approved packages

 

Rust : Cargo

 

  • use cargo-cooldown
  • Cargo.lock provides deterministic dependency locking
  • alternative registries can restrict dependency sources
  • cargo vet and cargo audit help validate dependencies
  • Cargo has fewer install-time execution paths than NPM ecosystems

 

πŸ“‹SUMMARY CHECKLIST

 

  • Use a cooldown period for package updates
  • Restrict package source locations
  • Lock package versions
  • Switch to a more secure package manager if available, and keep it updated
  • Human verification of package updates

 

🍦BONUS for the TypeScript crowd

 

Now is a great time to consider upgrading from NPM to PNPM! While BUN is also a great choice, its a bit heavier of a lift.

In addition to the enhanced security, PNPM installs packages in a central location on your machine, and creating symlinks in your app specific packages folder. This means MUCH faster installs of common packages, and MUCH less wasted disk space especially for monorepos.

 

Moving from NPM to PNPM

 

# powershell / bash

 

npm install -g pnpm

rm -rf node_modules

rm package-lock.json

pnpm install

 

# ToDo: Apply the configuration suggestions from above to lock things down

 

 

References

 

 

#AiBetter