Many quirks come from abilities that were once deemed useful, such as compiling code in other languages after package install.
Sure, today, I can disable install scripts if I want but it doesn't change much when I eventually run code from the package anyway.
But even restricting access to the file system to the project's root folder would leave many doors open, with or without foreign languages: Node is designed as a general purpose JS runtime, including server-side and build-time usage.
The utility of node.js was initially to provide a JS API that, unlike the web platform, is not sandboxed. And npm is the default package manager.
This not only allows server-side usage, but also is essential to many early dev scenarios. Back in the days, it might have been SCSS builds using node-gyp (wouldn't recommend). Today it's things like Golang TypeScript or SSGs.
So, long story short: as many people before me already said, it's an ecosystem/cultural problem.
One thing against npm in this regard was/is its broken lock-file handling until I think version 12 or 16. That led to unintended transitive dependency version changes, breaking any reproducibility.
Same for compiling foreign languages.
These problems are solved today / not different from other package managers and -registries, as far as I know.
The culture of taking breaking changes and dependency bloat lightly has not changed as much, I think, although it's improved.
This most important point seems to be related to 3 reasons IMO:
- junior developers without experience in library development reaching large audiences
- specs, languages, runtime, and the package managers itself going through disruptions and evolutions
- rapidly releasing breaking majors, often caused by the above factors
The combination of these plus the role of the project lead/team who actually decides about the dependencies.
There are probably also many projects with unclear roles and many people who can push manifest changes, coupled with habitual access to CI/CD pipelines.