ARTICLE
Supply Chain Defense for Modern Package Managers
Modern software imports code from hundreds of strangers at build time, and AI-powered attackers are making that risk harder to ignore. Our Software Architect, Russ, breaks down the practical defences every engineering team should have in place across TypeScript, C#, Python, Kotlin, and Rust.
Russ Painter
Software Architect, 8 West Consulting
SHARE POST
June 08, 2026
π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:
|
|
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
-
Recent npm supply chain attacks video: https://www.youtube.com/watch?v=D13Q4Z7-4Oo
-
Matija Grcic dependency cooldown reference: https://github.com/matijagrcic/dependency-cooldowns
-
pnpm supply chain security: https://pnpm.io/supply-chain-security
-
Dependabot cooldown documentation: https://github.blog/changelog/2025-07-01-dependabot-supports-configuration-of-a-minimum-package-age/
#AiBetter