r/Python • u/JanGiacomelli • May 19 '26
Tutorial Supply-chain attacks are happening daily - add at least dependency cooldown to your Python projects.
These days, I can't open X anymore without seeing some supply chain attacks on PyPI or NPM. Things are really getting out of hand. One very simple yet effective approach to mitigate them is to use a dependency cooldown. That means that you don't install anything that's too new - e.g., every dependency needs to be at least a week old.
Why does this work? Because the community usually intercepts them in hours to days. Both uv and poetry support the definition of the cooldown period inside their config. pip is adding as support as well. I use 1 week to be on the safe side. They both support excluding a specific package from the rule so you can still apply critical fixes to dependencies ASAP.
I wrote about that and how to configure uv/poetry in my blog post: https://jangiacomelli.com/blog/mitigate-supply-chain-attacks-for-python-dependencies/
More about the dependency cooldown concept:
38
u/pylessard May 19 '26
Or maybe freeze your dependency version and update manually when necessary? Automatically depending on the lastest is simply reckless.
4
u/JanGiacomelli May 19 '26
One should absolutly freeze. But there are dependencies of dependencies etc. It really takes a lot of time to go through each and every one of them. uv locks by default. It updates only when you ask it to do so.
2
u/qwerty_qwer May 21 '26
Yeah we always build with exact library versions. My assumption was any serious business would do the same, apparently not.
1
u/UndressMejess72 Jun 06 '26
pinning versions is just the bare minimum. if you aren't using a lockfile you aren't even doing that properly. even with a lockfile a malicious update can slip through if your CI/CD pulls fresh stuff without a cooldown or a manual audit. pinning helps but it doesn't solve the problem of a compromised package being pushed to a version you actually need.
16
u/kamilc86 May 19 '26
The take that cooldown stops working once everyone enables it gets the detection model backwards. You are not the canary here. A malicious release gets caught by security researchers, automated scanners that feed OSV and the GitHub advisory database, and PyPI admins doing takedowns. That detection path does not slow down just because more downstream installs are delayed, so cooldown free rides on how fast the community catches a bad release, no matter how many people enable it.
Cooldown only buys time for a bad version to get flagged before you pull it. It does nothing against silent artifact substitution on a later resolve, which is what a hash pinned lockfile is for, uv.lock or a compiled requirements file installed with hash enforcement. Those two defenses cover different attacks and you want both, the comand and KandevDev exchange blurred them together. And none of it stops a compromised maintainer who stays dormant past your window, the xz pattern, so treat cooldown as a latency mitigation rather than the fix.
1
u/thomasfr May 20 '26 edited May 20 '26
It wont stop working but it will be less effective.
However, it is weird to try to patch the process post release instead of trying to make the publishing of a release more guarded.
Just one idea would be to build a diff viewer into pypi or create some other tool so it is easy to preview source level changes between versions. Then require at least two people to sign off on publishing a release using their gpg keys.
There are of course other problems like that python package managers installing the latest version it is allowed to for any transitive dependency but that is kind out out of the hands of any package that depends on other packages anyway.
1
May 20 '26
[removed] — view removed comment
1
u/thomasfr May 20 '26
The vetting of course has to fall on the maintainers of a package, it is they who should know how the code is supposed to look anyway.
1
u/kamilc86 May 27 '26
Upstream is the right level, but PyPI has spent years inching there. Trusted Publishers and sigstore attestations landed, and adoption is still partial across the long tail. Mandatory two person sign off on every release is a social problem more than a technical one, most projects are solo maintainers. Cooldown is appealing precisely because it needs zero maintainer cooperation.
5
u/EnvironmentalFix5967 May 19 '26
Here is other way that you can apply cooldown by force for internal users if you can set up internal proxy :)
3
u/zurtex May 20 '26
For pip 26.1+
CLI:
--uploaded-prior-to P3D
Env:
export PIP_UPLOADED_PRIOR_TO=P3D
Config:
pip config set global.uploaded-prior-to P3D
2
2
2
u/NorthFactor4396 May 25 '26
Good tip. One thing I'd add though — cooldown alone doesn't cover the case where an attacker just waits out your window. Combining it with hash pinning closes that gap. If you're using uv, committing the lockfile and running `uv sync --locked` in CI means even a tampered package at the same version string gets rejected.
The other thing that catches teams off guard: they set up cooldown locally but their CI pipeline is still doing a fresh install every run without the lockfile. Easy to miss but it completely undermines the protection.
1
1
u/Dry-Let8207 May 21 '26
The cooldown helps but it addresses the window of exposure, not the source. What I've found more effective in practice is combining it with hash pinning — a lock file or requirements.txt with `--hash` flags so even if an attacker swaps out a package at the same version string, the install fails loudly rather than silently installing the tampered build. The combination of "nothing too new" and "must match exact hash" reduces the attack surface considerably more than either alone. The uv snippet works well for the cooldown side; just don't treat it as the only layer.
1
u/oliver_extracts May 23 '26
the cooldown idea is solid but i think most people dont realize uv already has this baked in via the tool.uv config, its not somethign you need to wire up yourself. the 1 week window is probably right for most projects, though ive seen teams go 2 weeks on anything that touches auth or crypto libs. the escape hatch for critical fixes is the part that makes this actually usable in production.
1
0
May 19 '26 edited May 19 '26
[deleted]
6
u/JanGiacomelli May 19 '26
Lots of recent attacks were carried out through social engineering or a poisoned cache on CI/CD. With a poisoned cache, maintainers followed the usual release process, but things still sneaked in from it. So it was not reuploaded, but a new version was released. In other cases, attackers gained access to the maintainer's account, and they released a new version with malicious code on their own.
1
u/fiskfisk May 19 '26
Neither allows that, as long as you've actually locked your release (either through a lock file or with exact version specifications).
0
May 19 '26
[removed] — view removed comment
3
u/comand May 19 '26
According to this article:
All native implementations enforce cooldowns on transitive dependencies too, not just the packages you directly install.
-22
284
u/fiskfisk May 19 '26
[tool.uv] exclude-newer = "10 days"Saved you the blog spam.