Shai-Hulud malware attack: Tinycolor and over 40 NPM packages compromised (socket.dev)

1233 points by jamesberthoty ↗ HN
A lot of blogs on this are AI generated and such as this is developing, so just linking to a bunch of resources out there:

Socket:

- Sep 15 (First post on breach): https://socket.dev/blog/tinycolor-supply-chain-attack-affect...

- Sep 16: https://socket.dev/blog/ongoing-supply-chain-attack-targets-...

StepSecurity – https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-p...

Aikido - https://www.aikido.dev/blog/s1ngularity-nx-attackers-strike-...

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...

128 comments

[ 3.2 ms ] story [ 88.5 ms ] thread
We've seen many reports of supply chain attacks affecting NPM. Are these symptoms of operational complexity, which can affect any such service, or is there something fundamentally wrong with NPM?
New day, new npm malware. Sigh..
Is there a theoretical framework that can prevent this from happening? Proof-carrying code?
I think I’m gonna start wrapping most of my build commands (which are inside a makefile) with bwrap. Allow network access only for fetching dependencies. Limit disk access (especially rw access) throughout. Etc.
My main takeaway from all of these is to stop using tokens, and rely on mechanisms like OIDC to reduce the blast radius of a compromise.

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?

I knew npm was a train wreck when I first used it years ago and it pulled in literally hundreds of dependencies for a simple app. I avoid anything that uses it like the plague.
Warning: LLM-generated article, terribly difficult to follow and full of irrelevant details.
I'm coming to the unfortunate realizattion that supply chain attacks like this are simply baked into the modern JavaScript ecosystem. Vendoring can mitigate your immediate exposure, but does not solve this problem.

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.

This is going to become an issue for a lot of managers, not just npm. Npm is clearly a very viable target right now, though. They're going to get more and more sophisticated.
post-install seems like it shouldn't be necessary anyway, let alone need shell access. What are legitimate JS packages using this for?
When the left-pad debacle happened, one commenter here said of a well known npm maintainer something to the effect of that he's an "author of 600 npm packages, and 1200 lines of JavaScript".

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?

Unless npm infrastructure will be thoroughly curated and moderated, it always going to stay a high risk threat.
(comment deleted)
This happens because there's no auditing of new packages or versions. The distro's maintainer and the developer is the same person.

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.

Right. Like NPM, Debian also supports post-install hooks for its packages. Not great (ask Michael Stapelberg)! But this is still a bit better than the NPM situation because at least the people writing the hooks aren't the people writing the applications, and there's some standards for what is considered sane to do with such hooks, and some communal auditing of those hooks' behavior.

Linux distros could still stand to improve here in a bunch of ways, and it seems that a well-designed package ecosystem truly doesn't need such hooks at the level of the package manager at all. But this kind of auditing is one of the useful functions of downstream software distros for sure.

> The general solution is to do what Debian does.

The problem with this approach is that frameworks tend to "expire" pretty quickly and you can't run anything for too long on Debian until the framework is obsolete. What I mean by obsolete is Debian 13 ships with Golang 1.24, A year from now it's gonna be Golang 1.26 - that is not being made available in trixie. So you have to find an alternative source for the latest golang deb. Same with PHP, Python etc. If you run them for 3 years with no updated just some security fixes here and there, you're gonna wake up in a world of hurt when the next stable release comes out and you have to do en-masse updates that will most likely require huge refactoring because syntax, library changes and so on.

And Javascript is a problem all by itself where versions come up every few months and packages are updated weekly or monthly. You can't run any "modern" app with old packages unless you accept all the bugs or you put in the work and fix them.

I am super interested in a solution for this that provides some security for packages pushed to NPM (the most problematic repository). And for distributions to have a healthy updated ecosystem of packages so you don't get stuck who knows for how long on an old version of some package.

And back to Debian, trixie ships with nginx 1.26.3-3+deb13u1. Why can't they continuously ship the latest stable version if they don't want to use the mainline one?

NX NPM attack (at least the previous wave which targetted tinycolor) relied on running post-install scripts. Go tooling does not give you ways to run post-install scripts, which is much more reasonable approach.
Pretty unfeasible with the variety of packages/ecosystems that get created. You'd either ending up requiring a LOT of dev time looking over packages on the maintainer end, or basically having no packages people need to use in your repository.

Finding the balance of that seems to me like it'd be incredibly difficult.

(comment deleted)
My comment yesterday, which received one downvote and which I will repeat if/until they’re gone: HTTP and JS have to go. There are ways to replace them.
Need to stop using javascript on desktop ASAP. Also Rust might be a bit dangerous now?
I think these kinds of attack would be strongly reduced if js had a strong standard library.

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.

Node.js has been adding APIs that make it feasible to write stuff without dependencies, it's slowly getting there.
> Shai Hulud

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...

Languages/VMs should support capability-based permissions for libraries, no library should be able to open a file or do network requests without explicit granular permissions.
As a developer, is there a way on mac to limit npm file access to the specific project? So that if you install a compromised package it cannot access any data outside of your project directory?