This. But the problem seems to go way deeper than npm or whatever package manager is used. I mean, why is anyone consuming a package like colors or tinycolors? Do projects really need to drag in a random dependency to handle these usecases?
There will always be packages that for some people are "but why?" but for others are "thank god I don't have to deal with that myself". Sure, colors and whatnot are tiny packages we probably could do without, but what are you really suggesting here? Someone sits and reviews every published package and rejects it if the package doesn't fit your ideal?
But the issue isn't just about the “thank god I don't have to deal with that myself” perspective. It's more about asking: do you actually need a dependency, or do you simply want it?
A lot of developers, especially newer ones, tend to blur that distinction. The result is an inflated dependency tree that unnecessarily increases the attack surface for malware.
The "ship fast at all costs" mindset that dominates many startups only makes this worse, since it encourages pulling in packages without much thought to long-term risk.
Why are React devs pulling object utils from lodash instead of reimplementing them?
Stuff like intents "this is a math library, it is not allowed to access the network or filesystem".
At a higher level, you have app sandboxing, like on phones or Apple/Windows store. Sandboxed desktop apps are quite hated by developers - my app should be allowed to do whatever the fuck it wants.
I would have thought it wouldn't be too hard to design a capability system in JS. I bet someone has done it already.
Of course, it's not going to be compatible with any existing JS libraries. That's the problem.
When declaring dependencies, you'd also declare the permissions of those dependencies. So a package like `tinycolor` would never need network or disk access.
Clojars (run by volunteers AFAIK) been doing signatures since forever, not sure why it's so difficult for Microsoft to follow their own yearly proclamation of "security is our top concern".
> The NPM CI tokens that don't require 2fa kind of makes it less useful though
Use OIDC to publish packages instead of having tokens around that can be stolen or leaked https://docs.npmjs.com/trusted-publishers
Do not let code to have access to things it's not supposed to access.
It's actually that simple. If you implemented a function which formats a string, it should not have access to `readFile`, for example.
Retrofitting it into JS isn't possible, though, as language is way too dynamic - self-modifying code, reflection, etc, means there's no isolation between modules.
In a language which is less dynamic it might be as easy as making a white-list for imports.
The situation gets better in monadic environments (can't readFile without the IO monad, and you cant' call anything which would read it).
Programming languages which are "static" (or, basically, sane) you can identify all imports of a module/library, and, basically, ban anything which isn't "pure" part of stdlib.
If your module needs to work with files, it will receive an object which lets it to work with files.
A lot of programming languages implement object-capability model: https://en.m.wikipedia.org/wiki/Object-capability_model it doesn't seem to be hard at all. It's just programmers have preference for shittier languages, just like they prefer C which doesn't even have language-level array bound checking (for a lack of a "dynamic array" concept on a language level).
I think it's sort of orthogonal to "pure functional" / monadic: if you have unrestricted imports you can import some shit like unsafePerformIO, right? You have another level of control, of course (i.e. you just need to ban unsafePerformIO and look for unlicensed IO) but I don't feel like ocap requires Haskell
This is something I'm trying to polish for my system now, but the idea is: yarn (and bundler and others) needs to talk only to the repositories. That means yarn install is only allowed outbound connections to localhost running a proxy for packages. It can only write in tmp, its caches, and the current project's node_packages. It cannot read home files beyond specified ones (like .yarnrc). The alias to yarn strips the cloud credentials. All tokens used for installation are read-only. Then you have to do the same for the projects themselves.
On Linux, selinux can do this. On Mac, you have to fight a long battle with sandbox-exec, but it's kinda maybe working. (If it gained "allow exec with specified profile", it would be so much better)
But you may have guessed from the description so far - it's all very environment dependent, time sink-y, and often annoying. It will explode on issues though - try to touch ~/.aws/credentials for example and yarn will get killed and reported - which is exactly what we want.
But internally? The whole environment would have to be redone from scratch. Right now package installation will run any code it wants. It will compile extensions with gyp which is another way of custom code running. The whole system relies on arbitrary code execution and hopes it's secure. (It will never be) Capabilities are a fun idea, but would have to be seriously improved and scoped to work here.
How many tokens do you have lying around in your home directory in plain text, able to be read by anything on your computer running as your user?
Zero? How many developers have plain-text tokens lying around on disk? Avoiding that been hammered into me from every developer more senior than me since I got involved with professional software development.
The approach also depends on the project. There is a bunch of different approaches and I don't think there is one approach that would work for every project, and sometimes I requires some wrangling but takes 5-10 minutes tops.
Some basic information about how you could make it work with 1Password: https://developer.1password.com/docs/cli/secrets-environment...
Edit: Testing 1Password myself, with 1password desktop and shell, if I have authed myself once in shell, then "spawn" would be able to get all of my credentials from 1Password.
So I'm not actually sure how much better than plaintext is that. Unless you use service accounts there.
With that said, it's not impossible some tool leaks their secrets into ~/.local, ~/.cache or ~/.config I suppose.
I thought they were referencing the common approach of adding environment variables with plaintext secrets to your shell config or as an individual file in $HOME, which been a big no-no for as long as I can remember.
I guess I'd reword it to "I'm not manually putting any cleartext secrets on disk" or something instead, if we wanted it to be 100% accurate.
Most of them. Mainly on purpose, (.env files) but many also accidentally. (shell history with tokens in the commands)
Frankly, our desktop OSes are not fit for purpose anymore. It's nuts that everything I run can instantly own my entire user account.
It's the old https://xkcd.com/1200/ . That's from 2013 and what little (Flatpak, etc.) has changed has only changed for end users - not developers.
I try to avoid JS, as it is a horrible language, by design. That does include TS, but it at least is useable, but barely - because it still tied to JS itself.
Still, even I who'd call myself a JavaScript developer also try to avoid desktop applications made with just JS :)
It is full of gotchas that serves 0 purpose nowadays.
Also remember that it is basically a Lisp wearing Java skin on top, originally designed in less than 2 weeks.
Typescript is one of few things that puts safety barrier and sane static error checking that makes JS bearable to use - but it still has to fall down to how JS works in the end so it suffers from same core architectural problems.
What some people see as a fault, others see as a feature :) For me, that's there to prevent entire websites from breaking because some small widget in the bottom right corner breaks, for example. Rather than stopping the entire runtime, it just surfaces that error in the developer tools, but lets the rest to continue working.
Then of course entire web apps crash because one tiny error somewhere (remember seeing a blank page with just some short error text in black in the middle? Those), but that doesn't mean that's the best way of doing things.
> Also remember that it is basically a Lisp wearing Java skin on top
I guess that's why I like it better than TS, that tries to move it away from that. I mainly do Clojure development day-to-day, and static types hardly ever gives me more "safety" than other approaches do. But again, what I do isn't more "correct" than what anyone else does, it's largely based on "It's better for me to program this way".
the issue is that it prevents that, but also allows you to send complete corrupt data forward, that can create horrible cascade of errors down the pipeline - because other components made assumption about correctness of data passed to them.
Such display errors should be caught early in development, should be tested, and should never reach prod, instead of being swept under the rug - for anything else other than prototype.
but i agree - going fully functional with dynamic types beats average JS experience any day. It is just piling up more mud upon giant mudball,
systems? rust - but it is still far from perfect, too much focus on saving few keystrokes here and there.
general purpose corporate development? c# - despite current direction post .net 5 of stapling together legacy parts of .net framework to .net core. it does most things good enough.
scripting, and just scripting? python.
web? there's only one, bad, option and that's js/ts.
most hated ones are in order: js, go, c++, python.
go is extremely infuriating, there was a submission on HN that perfectly encapsulated my feelings about it, after writing it for a while: https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-...
We fcked up with js, big time and its with us forever now
But apparently they only made it do like 95% of what JS does so you can't actually replace js with it. To me it seems like a huge blunder. I don't give a crap about making niche applications a bit faster, but freeing the web from the curse of JS would be absolutely huge. And they basically did it except not quite. It's so strange to me, why not just go the extra 5%?
The only way to remove Js is to create a new browser that doesn't use it. Fragments the web, yes and probably nobody will use it
You realize my point right? People are taught to not reinvent the wheel at work (mostly for good reasons) so that's what they do, me and you included.
You ain't gonna be bothered to write html and manual manipulation, the people that will give you libraries to do so won't be bothered reimplementing parsers and file watchers, file watcher writers won't be bothered reimplementing file system utils, file system utils developers won't be bothered reimplementing structured cloning or event loops, etc, etc.
I myself just the other day had the task of converting HTML to markdown, because I don't remember whether it was Jira or Github APIs that returns comments as HTML and despite it being mostly few hours of work that would get us 90% there everybody was in favor of pulling a dependency to do so (with its own dependencies) and thus further exposing our application to those risks.
https://github.com/williamcotton/markdown-to-html-llm
;)
These attacks may just be the final push I needed to take server rendering (without js) more seriously. The HTMX folks convinced me that I can get REALLY far without any JavaScript, and my apps will probably be faster and less janky anyway.
> Server-side-rendering without JavaScript is just back to the stuff Perl and PHP give you.
As well as Ruby, Python, Go, etc.
There is a difference, but it's not an order of magnitude and neither is a true island.
Granted, deciding not to use JS on the server is reasonable in the context of this article, but for the client htmx is as much a js lib with (dev) dependencies as any other.
https://github.com/bigskysoftware/htmx/blob/master/package.j...
Unless this lack of scrutiny is exclusive to JavaScript ecosystem, then this attack could just as well have happened in Rust or Golang.
I just went to crates.io and picked a random newly updated crate, which happened to be pixelfix, which fixes transparent pixels in pngs.
It has six dependencies and hundreds of transient dependencies, may of which appear to be small and highly specific a la left-pad.
https://crates.io/crates/pixelfix/0.1.1/dependencies
Maybe this package isn't representative, but it feels pretty identical to the JS ecosystem.
Just defending Rust.
> 5 remaining dependencies have lots of dependencies of their own.
Mostly well-known crates like rayon, crossbeam, tracing, etc.
Languages without package managers have a lot more friction to pull in dependencies. You usually rely on the operating system and its package-manager-humans to provide your dependencies; or on primitive OSes like Windows or macOS, you package the dependencies with your application, which involves integrating them into your build and distribution systems. Both of those involve a lot of manual, human effort, which reduces the total number of dependencies (attack points), and makes supply-chain issues like this more likely to be noticed.
The language package managers make it trivial to pull in dozens or hundreds of dependencies, straight from some random source code repository. Your dependencies can add their own dependencies, without you ever knowing. When you have dozens or hundreds of unvetted dependencies, it becomes trivial for an attacker to inject code they control into just one of those dependencies, and then it's game over for every project that includes that one dependency anywhere in their chain.
It's not impossible to do that in the OS-provided or self-managed dependency scenario, but it's much more difficult and will have a much narrower impact.
Supply chain attacks happen at every layer where there is package management or a vector onto the machine or into the code.
What NPM should do if they really give a shit is start requiring 2FA to publish. Require a scan prior to publish. Sign the package with hard keys and signature. Verify all packages installed match signatures. Semver matching isn’t enough. CRC checks aren’t enough. This has to be baked into packages and package management.
While technically true, I have yet to see Go projects importing thousands of dependencies. They may certainly exist, but are absolutely not the rule. JS projects, however...
We have to realize, that while supply chain attacks can happen everywhere, the best mitigations are development culture and solid standard library - looking at you, cargo.
I am a JS developer by trade and I think that this ecosystem is doomed. I absolutely avoid even installing node on my private machine.
> cat go.sum |awk '{print $1}' | sort |uniq |wc -l
431
> wc -l go.sum
1156 go.sum
Assuming 'go mod tidy' is periodically run go.mod should contain all dependencies (which in this case seems to be shy of 300, still a lot).
NPM has also been sending out nag emails for the last 2+ years about 2FA. If anything, that probably helped the attack on the Junon account that we saw a couple weeks ago.
That is, if some attacker create some dummy trivial but convenient package and 2 years latter half the package hub depends on it somehow, the attacker will just use its legit credential to pown everyone and its dog. This is not even about stilling credentials. It’s a cultural issue with bare blind trust to use blank check without even any expiry date.
That's really the core issue. Developer-signed packages (npm's current attack model is "Eve doing a man-in-the-middle attack between npm and you," which is not exactly the most common threat here) and a transparent key registry should be minimal kit for any package manager, even though all, or at least practically all, the ecosystems are bereft of that. Hardening API surfaces with additional MFA isn't enough; you have to divorce "API authentication" from "cryptographic authentication" so that compromising one doesn't affect the other.
I see this odd take a lot - the automatic narrowing of the scope of an attack to the single ecosystem it occurred in most recently, without any real technical argument for doing so.
What's especially concerning is I see this take in the security industry: mitigations put in place to target e.g. NPM, but are then completely absent for PyPi or Crates. It's bizarre not only because it leaves those ecosystems wide open, but also because the mitigation measures would be very similar (so it would be a minimal amount of additional effort for a large benefit).
I ask because think the directionality is backwards here: I’ve been involved in packaging ecosystem security for the last few years, and I’m generally of the opinion that PyPI has been ahead of the curve on implementing mitigations. Specifically, I think widespread trusted publishing adoption would have made this attack less effective since there would be fewer credentials to steal, but npm only implemented trusted publishing recently[1]. Crates also implemented exactly this kind of self-scoping, self-expiring credential exchange ahead of npm.
(This isn’t to malign any ecosystem; I think people are also overcorrect in treating this like a uniquely JavaScript-shaped problem.)
[1]: https://github.blog/changelog/2025-07-31-npm-trusted-publish...
But don't brush off "special status" of NPM here. It is unique in that JS being language of both front-end and back-end, it is much easier for the crooks to sneak in malware that will end up running in visitor's browser and affect them directly. And that makes it a uniquely more attractive target.
1- thoroughly and fully analyze any dependency tree you plan to include 2- immediately freeze all its versions 3- never update without very good reason or without repeating 1 and 2
in other words: simply be professional, face logical consequences if you aren't. if you think one package manager is "safer" than others because magic reasons odds are you'll find out the hard way sooner or later.
As far as I know crates.io has everything that npm has, plus
- strictly immutable versions[1]
- fully automated and no human in the loop perpetual yanking
- no deletions ever
- a public and append only index
Go modules go even further and add automatic checksum verification per default and a cryptographic transparency log.
Contrast this with docker hub for example, where not even npm's basic properties hold.
So, it is more like
docker hub ⊂ npm ⊂ crates.io ⊂ Go modules
[1] Nowadays npm has this arguably too
At some point people need to realize and go back to writing vanilla js, which will be very hard.
The rust ecosystem is also the same. Too much dependence on packages.
An example of doing it right is golang.
However, processes and practices around NodeJS and npm are in dire need of a security overhaul. leftpad is a cultural problem that needs to be addressed. To start with, snippets don't need to be on npm.
Use pnpm and whitelist just what you need. It disables all scripts by default.
The malware could have been a JS code injected into the module entry point itself. As soon as you execute something that imports the package (which, you did install for a reason) the code can run.
I don't think that many people sandbox their development environments.
Modern node package managers such as yarn and pnpm allow you to prevent post installs entirely.
Today most of the time you need to make an exception for a package is when a module requires native compilation or download of a pre-built binary. This has become rare though.
In fact this blog post appears to be advertising for a system that secures build pipelines.
Google has written up some about their internal approach here: https://cloud.google.com/docs/security/binary-authorization-...
Not much has changed since then. The best counter-example I know is esbuild, which is a fully featured bundler/minifier/etc that has zero external dependencies except for the Go stdlib + one package maintained by the Go project itself:
https://www.npmjs.com/package/esbuild?activeTab=dependencies
https://github.com/evanw/esbuild/blob/755da31752d759f1ea70b8...
Other "next generation" projects are trading one problematic ecosystem for another. When you study dependency chains of e.g. biomejs and swc, it looks pretty good:
https://www.npmjs.com/package/@biomejs/biome/v/latest?active...
https://www.npmjs.com/package/@swc/types?activeTab=dependenc...
Replacing the tire fire of eslint (and its hundreds to low thousands of dependencies) with zero of them! Very encouraging, until you find the Rust source:
https://github.com/biomejs/biome/blob/a0039fd5457d0df18242fe...
https://github.com/swc-project/swc/blob/6c54969d69551f516032...
I think as these projects gain more momentum, we will see similar things cropping up in the cargo ecosystem.
Does anyone know of other major projects written in as strict a style as esbuild?
These kind of projects usually are pretty great because they aim to work with CGO_ENABLED=0 so the libs are very portable and work with different syscall backends.
Additionally I really like to go mod vendor my snapshot of dependencies which is great for short term fixes, but it won't fix the cause in the long run.
However, the go ecosystem is just as vulnerable here because of lack of signing off package updates. As long as there's no verification possible end-to-end when it comes to "who signed this package" then there's no way this will get better.
Additionally most supply chaib attacks focussed on the CI/CD infrastructure in the past, because they are just as broken with just as many problems. There needs to be a better CI/CD workflow where signing keys don't have to be available on the runners themselves, otherwise this will just shift the attack surface to a different location.
In my opinion the package managers are somewhat to blame here, too. They should encourage and mandate gpg signatures, and especially in git commits when they rely on git tags for distribution.
There are plenty of people in the community who would help reduce the number of dependencies, but it really requires the maintainers to make it a priority. Otherwise the only way to address it is to switch to another solution like oxlint.
The general solution is to do what Debian does.
Keep a stable distro where new packages aren't added and versions change rarely (security updates and bugfixes only, no new functionality). This is what most people use.
Keep a testing/unstable distro where new packages and new versions can be added, but even then added only by the distro maintainer, NOT by the package developers. This is where the audits happen.
NPM, Python, Rust, Go, Ruby all suffer from this problem, because they have centralized and open package repositories.
Benefit from this feature.
Aikido - https://www.aikido.dev/blog/s1ngularity-nx-attackers-strike-...
Socket - https://socket.dev/blog/ongoing-supply-chain-attack-targets-...
Ox - https://www.ox.security/blog/npm-2-0-hack-40-npm-packages-hi...
Safety - https://www.getsafety.com/blog-posts/shai-hulud-npm-attack
Phoenix - https://phoenix.security/npm-tinycolor-compromise/
Semgrep - https://semgrep.dev/blog/2025/security-advisory-npm-packages...
NPM debug and chalk packages compromised (1366 points, 754 comments): https://news.ycombinator.com/item?id=45169657
Aikido says: > We were alerted to a large-scale attack against npm...
Socket says: > Socket.dev found compromised various CrowdStrike npm packages...
Ox says: > Attackers slipped malicious code into new releases...
Safety says: > The Safety research team has identified an attack on the NPM ecosystem...
Phoenix says: > Another supply chain and NPM maintainer compromised...
Semgrep says: > We are aware of a number of compromised npm packages
And then vendors from Socket, Aikido, and Step all seem to have detected it via their upstream malware detection feeds - Socket and Aikido do AI code analysis, and Step does eBPF monitoring of build pipelines. I think this was widespread enough it was noticed by several people.
If it was provided, it would significantly trim dependency trees of all the small utility libraries.
Perhaps we need a common community effort to create a “distro” of curated and safe dependencies one can install safely, by analyzing the most popular packages and checking what’s common and small enough to be worth being included/forked.
Clever name... but I would have expected malware authors to be a bit less obvious. They literally named their giant worm after a giant worm.
> At the core of this attack is a ~3.6MB minified bundle.js file
Yep, even malware can be bloated. That's in the spirit of NPM I guess...
My posts way before the issue was created: https://news.ycombinator.com/item?id=45252940 https://www.linkedin.com/posts/daniel-pereira-b17a27160_i-ne...
This isn't a JavaScript problem. What, structurally, stops the same thing happening to PyPI? Or the Rust ecosystem? Or Lisp via QuickLisp? Or CPAN?
This whole mess was foreseeable. Everyone you talked to about it fell into one of two camps: 1) terrified (tiny minority) and 2) "hasn't been a problem yet. Package manager go brrr"
Sometimes, when people predict a disaster, they're just being chicken littles and worrying about nothing. But sometimes they're right and the people brushing security concerns aside end up looking ridiculous.
So what's to be done?
Look. Any serious project needs to start vendoring its dependencies. People should establish big, coarse grained meta-distributions like C++ Boost that come from a trustable authority and that get updated infrequently enough that you can keep up with release notes.
In any case, does anyone have an exhaustive list of all recently compromised npm packages + versions across the recent attacks? We need to do an exhaustive scan after this news...
Its less a technical but rather a moral hurdle. Its probably a bunch of teenagers behind it like how it was with the Mirai Botnet.
gchamonlive•2h ago
dist-epoch•1h ago
NPM packages are used by huge Electron apps like Discord, Slack, VS Code, the holy grail would be to somehow slip something inside them.
anthk•1h ago
guidedlight•1h ago
mr_toad•1h ago
ehnto•1h ago
It's the new pragmatic choice for web apps and so it's everyone is using it, from battle hardened teams to total noobs to people who just don't give a shit. It reminds me of Wordpress from 10 years ago, when it was the goto platform for cheap new websites.
LeifCarrotson•1h ago
In our line-of-business .NET app, we have a logger, a database, a unit tester, and a driver for some specialty hardware. We upgrade to the latest version of each external dependency about once per year (every major version) to avoid accruing tech debt. They're all pinned and locally hosted, nuget exists but we (like most .Net developers) don't use it to the extent that npm devs do. We read the changelogs - all four of them! - and manually update.
I understand that the NPM ecosystem works differently from a "batteries included" .Net environment for a desktop app, but it's not just about where the users are. Line of business code in .Net and Java apps process a lot of important data. Slipping a malicious package into pypi could expose all kinds of juicy, proprietary data, but again, it's less about the existence of a package manager and more about when and how you use it.
gchamonlive•1h ago
liveoneggs•1h ago
koakuma-chan•1h ago
Its users don't check who the email is from
palmfacehn•1h ago
I would argue that is only one of the many issues with the JS/TS/NPM ecosystem. Many of the other problems have been normalized. The constant security issues are highly visible.
eastbound•1h ago
skydhash•1h ago
jsiepkes•1h ago
Where did you see that number? Maven central says it has about 18 million [1] packages. Maybe with all versions of those 18 million packages there are about 62 million artifacts?
While the Java ecosystem is vastly larger, in Java (with Maven, Gradle, Bazel, etc.) it is not common to use really small libraries. So you end up with vastly less transitive dependencies in your projects.
[1] https://mvnrepository.com/repos/central
palmfacehn•1h ago
hannob•1h ago
Adding dependencies comes with advantages and downsides. You need to strike a balance between them. External libraries can help implement things that you better don't implement yourself, so the answer is certainly not "no dependencies". But there are downsides and risks, and the risks grow with the number of dependencies.
In the world of NPM, people think those simple truths don't apply to them and the downsides and risks of dependencies can be ignored. Then you end up with thousands of transitive dependencies.
They're wrong and learn it the hard way now.
Intermernet•1h ago
DimmieMan•1h ago
Community is very happy to pick up helper libraries and by the time you get all the way up the tree in a react framework you have hundreds or even thousands of packages.
If you’re sensible you can be fine just like any other ecosystem, but limited because one wrong package and you’ve just ballooned your dependency tree by hundreds which lowers the value of the ecosystem.
Node doesn’t have a standard library and until recently not even a test runner which certainly doesn’t help.
If your sensible with node or Deno* you’ll somewhat insulated from all this nonsense.
*Deno has linting,formatting,testing & a standard library which is a massive help (and a permission system so packages can’t do whatever they want)
karel-3d•1h ago
It's just javascript being javascript.
Sammi•50m ago