I came across this interesting rant the other day: https://github.com/uNetworking/uWebSockets.js/blob/master/mi...
It does make sense that the right way would be to fork every dependency you use and install from your own repo reviewing and merging from upstream as needed. Would be a giant PITA though. :)
The problem would be the dependencies of your dependencies, and keep going many levels.
That might change the odds, but unless you fork diligently (and monkeypatch each and every future vulnerability) you might ship a compromised fork forever.
Shouldn't you also restrict version number for a package not just the latest?
I think the general idea that your supply chain should be rooted in source repositories and associated commit hashes is the right one. Tooling can be made to automate the process of putting together a product from those defined sources. Some languages/systems already have some support for this. E.g. Golang and Rust. The concept of a "binary" artifact is really dead now everyone uses git and builds are quick. It lives on in things like npm and docker hub but we don't actually need it.
The problem is the volume of dependencies. Most modern JavaScript, Python, Rust, Go, etc. projects have many dozens of transitive dependencies.
For smaller shops (by small I mean <1,000 employees) this isn't even tenable. We (engineering team of about 10 people) mitigate what we can via tooling and cooldown periods/minimum release age. This will work as long as these malicious packages remain reasonably detectable. I think that's the proper balance, because we can adjust the # of days we are willing to risk against the SOTA of detection tooling.
Nothing that couldn't be automated; in Go land this is (arguably) called vendoring (https://go.dev/ref/mod#vendoring). Good to offload or reduce dependencies on 3rd party dependency hosters, pull a dependency into your own code review tools, and to ensure reproducible builds long term.