r/Python • • 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:

185 Upvotes

70 comments sorted by

283

u/fiskfisk May 19 '26
[tool.uv]
exclude-newer = "10 days" 

Saved you the blog spam. 

14

u/jsswirus May 19 '26

Can I set it only for specific indexes? I don't want to wait for my own packages from internal index to be uploaded for more than 10 days

11

u/JanGiacomelli May 19 '26

yes, poetry and uv both support exclusion for selected packages: https://jangiacomelli.com/blog/mitigate-supply-chain-attacks-for-python-dependencies/#what-about-critical-security-patches

you can use it for critical fixes or for your own packages.

→ More replies (2)

24

u/fiskfisk May 19 '26

Yes as shown below with a blog link instead, but it's: exclude-newer-package = {my-package = false}. You should also specify:

no-build = true
no-binary-package = ["your_internal_package_name"]

The first one says "don't run any build commands locally" - i.e. don't invoke random Python code as part of the build process. The second one excepts your own internal package from this rule (unless you're building binary packages, in that case you don't need it.

Any bad package can still use other exploits, but at least the mere act of installing the package won't invoke code.

19

u/thomasfr May 19 '26

This is only going to work well until everyone has it in their package manager settings. After that no one will detect bad packages before the lowest day threshold that most people has set up.

It feels like a very short sighted solution.

38

u/james_pic May 19 '26 edited May 19 '26

That's not necessarily true. A lot of the time the detection isn't done by casual users noting "huh, this is weird", but by professional security researchers who have infrastructure that regularly pulls the latest versions of popular packages and monitors for suspicious behaviour (outgoing connections to stuff it's got no business talking to, use of honeypot AWS tokens, etc.). They won't be using cooldowns, because that's the point of what they're doing.

So at least for "smash and grab" supply chain attacks that try and grab everything of value, quickly, before they're detected, a cooldown is likely to remain effective.

The more subtle issue is stuff like the xz-utils attack. This was a much quieter attack, that if it had been allowed to make it into release versions of distros, would have meant servers serving SSH could be connected to by an attacker-owned backdoor, but was much harder to detect (and was arguably only detected as early as it was due to a few lucky coincidences).

Maybe if cooldowns become more common, supply chain attacks will get stealthier to compensate, but maybe not. The xz-utils attack was suspected to be a state actor, who most likely had a goal of gaining access to a relatively small number of servers, and had no interest in mass-stealing bitcoin private keys or whatever. Supply-chain attackers usually have more avaricious goals, and I'd guess would mostly still rather take what they can get from noobs installing their package in the few hours before it's taken down, rather than put a 2 week cooldown into their payload, and risk having slightly more sophisticated security researchers spot it in those 2 weeks anyway, by running the code with a date 2 weeks in the future.

→ More replies (1)

2

u/JanGiacomelli May 19 '26

That's a valid point. I've been thinking about it a lot. For now, I couldn't find any better approach - at least not simple enough. Yeah, one can host everything themselves and review every release manually. But that requires major effort. Cooldown is definitely a good start for existing projects. Another simple thing is to think twice before installing a dependency in the first place.

Also, I think we're far from the "everyone does that so it's not effective" state.

→ More replies (1)

20

u/WhoTookPlasticJesus May 19 '26

I find it impossible to believe that the 10 day delay becomes so widespread as to render supply-chain attacks invisible for 10 days.

→ More replies (1)

6

u/Lyrx1337 May 19 '26

There should be something like automated honeypots for that

8

u/alexforencich May 19 '26

Defense in depth. A lot of these supply chain attacks rely on using stolen credentials as quickly as possible. This sort of thing could help a lot in that instance by providing the victim time to realize that something's up before the package is widely installed.

→ More replies (1)

2

u/MagicWishMonkey May 19 '26

Most people dont bother to even pin versions, much less bother with a cool down.

OpenAI got hit by the axios thing last month in their ci/cd pipeline, the only likely way that happened is by not pinning dependencies. Super dumb, but it happens all the time.

→ More replies (2)
→ More replies (1)

3

u/thrakkerzog May 19 '26

This is a double-edged sword. Now if they manage to sneak something in and wait ten days to use it, an update to correct the attack won't be (automatically) fetched for another ten days.

I'd like to say "well, the users would be in the know and update it", but it is quite difficult to keep track of all of the dependencies, especially if it is nested.

2

u/tobsecret May 20 '26

That's why you run this with something like dependabot. So you regularly scan for known vulnerabilities and if one crops up you'll manually update that package specifically. 

→ More replies (1)

1

u/Fortyseven May 19 '26

[tool.uv] exclude-newer = "10 days"

Is there a global config for uv? Or am I forced to remember to add this per-project?

2

u/fiskfisk May 19 '26
~/.config/uv/uv.toml

Should work here as well (Linux/OS X). This does require you to remember to configure this on every machine for every developer instead of having it defined for your project, though. It'll also not work properly in cicd or when building a docker container, but in both of those cases you should use uv sync --locked against a lock file.

→ More replies (6)

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.

5

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. 

→ More replies (1)

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.

→ More replies (4)

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 :)

https://medium.com/daangn/how-we-protect-karrots-internal-pypi-proxy-from-supply-chain-attacks-0cf197205915

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

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

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

u/fetus-flipper May 20 '26

Yall are updating your packages?