Home > Supply Chain Attacks on Open Source

2026 Aug

Over the past 12 months, there's been a number of attacks against npm - the largest package repository in the world, and home to the JavaScript ecosystem. These attacks are notable because they were able to quickly add malware to hundreds of popular open source packages over the course of hours, which was quite disruptive to the ecosystem.

Credential stealing malware is not new to npm. Here we have a graph from the GitHub advisory database, where we publish information everytime malware is taken down from npm. However, previously this malware was added as new packages, whereas these attacks were able to add malware to existing popular packages.

These attacks were also unique in that they didn't make use of a single security vulnerability, instead stitching together several gaps for maximum impact. We'll first talk about the preconditions that enabled these attacks, then go through the attack step-by-step, walk through mitigations we've deployed, and finally talk about future work.

5 years ago, the GitHub Security Lab published GitHub Actions Preventing Pwn Requests, describing an insecure configuration of Actions, which many folks use as their CI/CD system. Usually an Actions workflow is triggered by code landing in your main branch, but you can also trigger a workflow when a pull request is opened in your repository. If you check out code in these workflows, the code is whatever the user submitted in the pull request, not your repository, and is potentially malicious. We documented this insecure configuration, but as you can probably guess, that was not enough to stop people from accidentally configuring their workflows in this way.

Then two years ago Adnan Khan: GitHub Actions Cache Poisoning was published describing a shortcoming in GitHub Actions' cache feature. The cache is pretty straightforward: you might have steps in your build process that don't change very often and want to cache their result in order to speed up your builds. However, the cache lacked a way to specify who should have read vs write access, making it easy to abuse.

Around that same time, in response to the rise of credential-stealing malware on npm, we worked on OpenSSF: Trusted Publishing for all Package Repositories. Trusted publishing lets you remove publishing credentials from your workflow, and you can't exfiltrate what isn't there! This guidance was based off of PyPI's implementation of trusted publishing; PyPI is the Python ecosystem package repository and the second largest in the world.

A year later we were able to announce that npm Trusted Publishing is generally available, although of course it takes time for people to adopt a security capability that is newly available.

And so last year I presented USENIX Security '25: Securing Packages in npm, Homebrew, PyPI, Maven Central, and RubyGems, describing how we were working to secure several package repositories. The good news is that we were on the right track with our security improvements, but the bad news is we didn't get them out fast enough as the first wave of attacks started a month later.

To understand the attacks we'll use the OpenSSF SLSA framework. It starts with some entity writing code, hopefully a person. They submit their changes to source control, and when enough of these changes land the build system gets the code from source control, runs the build process, and sends the final build product to a package repository.

In this talk we'll mostly be talking about GitHub Actions as the build system and npm as the package repository. But part of the reason we're talking about this publicly is because these attacks work against other build systems and other package repositories, so we want folks who operate those systems to be aware of the risk and consider which of the mitigations we talk about might apply to their systems.

The stage is set, so now let's talk through what the attack looks like in detail. It's a fairly straightforward 3 step process, and our first step is to find a single popular open source package to compromise. Phishing is definitely not a new technique, and it has a very low hit rate, but remember that our first goal is just to compromise a single package.

Of course, that's not the only way we could compromise a package. Like we talked about earlier, attackers look for people using GitHub Actions with vulnerable workflow triggers, so that the code they submit in a pull request will be checked out and executed.

Sometimes a pwn request by itself is not sufficient, in which case we saw attackers chain together a pwn request in order to poison the cache, and then wait for a production build to use the poisoned cache entry.

In any case, at the end of step 1 we have a single malicious package in the npm registry. Now our goal is to get our credential-stealing malware executed.

Unfortunately, npm install scripts make this quite easy. This feature is many years old, and is used by packages that have architecture or OS-specific install instructions. But this also makes it easy for malware to run when a package is installed, so it doesn't even have to wait for you to call libraries in the package.

The malware can look for credentials from npm, GitHub, cloud providers, or any other content that can be exfiltrated for later exploitation.

Of course, build environments are also target-rich environments for attackers, and are also likely to have access to long-lived credentials. Worse, these credentials are often scoped to whatever the user who provisioned them had access to, so one token can be used to compromise multiple packages.

And that's exactly what we see happen. One compromised package leads to dozens, which leads to hundreds, in an astonishingly short period time. This is what makes these attacks uniquely impactful: how quickly they are able to compromise hundreds of packages.

So that's how the attack is carried out, so what are we going to do about it? Well, just like the attack didn't make use of a single vulnerability, so too does our defense rely on hardening every step of the software supply chain.

The first thing we did is put npm accounts into a read-only mode for 72 hours if they change their email or use a MFA recovery code. This gives maintainers time to respond if they realize their account was compromised, and slows down how quickly the attack happens.

Next we changed the way Actions' checkout step works so that it's aware of what triggered the workflow, as well as if the code it's being asked to check out comes from the repository or a pull request, and it will refuse to checkout code in situations it thinks is dangerous.

Technically this was a breaking change for a very small number of users, but after much deliberation we decided that given the waves of attacks this major security improvement was worth breaking a few users. We did provide a flag so that you could opt-in to the old behavior, if you were relying on it, until you found more time to move off this vulnerable pattern.

Another breaking change is that we default to read-only cache permission for these untrusted workflow triggers. Here again, this was a breaking change, but fortunately with a cache that just means that builds would take longer, not that they would stop working altogether.

It should not surprise you to learn I'm a huge fan of trusted publishing, and we are working on adding new providers to npm trusted publishing so more people can easily get long-lived credentials out of their build pipelines.

At the end of the day, on-by-default install scripts had to go, so they default to off in npm CLI v12. Again, this is a breaking change, but we provide a new CLI command, approve-scripts, so that you can list packages that you are expecting to make use of install scripts.

In our conversations with npm maintainers, we felt that this breaking change was the right move.

Speaking of the npm maintainer community, probably the most-requested feature was the ability to "stage" a package for publishing in the npm registry that could later be approved with a strongly authenticated session to the registry.

This is a great way for maintainers of very popular packages to opt in to an additional security capability, and the extra approval step disrupts the rapid spread of these attacks.

Another low-implementation effort capability to slow down the pace of these attacks is cooldowns. Instead of consuming packages as soon as they are published to the registry, we're encouraging tooling to provide the latest package version that is at least 3 days old.

This cooldown duration is configurable, so security researchers or other curious parties can get packages that have recently been submitted to the registry in order to look for malicious behavior.

Zooming out from these individual security capabilities, what did we learn? Just like there wasn't one vulnerability exploited, so too did we stitch together defensive capabilities. None of these capabilities is designed to completely stop attacks by itself, however they are all focused on reducing risk and slowing down the pace of the attack. Even if a single capability is subverted, together they should still dramatically reduce risk.

There's also an undeniable "secure by default" theme running through these mitigations. What we learned from pwn requests is that it's not sufficient to update documentation and expect millions of people to carefully read it. The default path needs to be the secure one.

The story does not end here. It's likely there will be attacks in the future, although hopefully they will be less severe. As more people adopt cooldowns, there's a number of interesting questions we can ask: what's the right cooldown period and what do we do when a known exploited vulnerability needs urgent patching?

Sometimes when people hear about these attacks they worry about the future of public registries, or even question the future of open source. While worrying, I'm confident that open source will survive this wave of attacks. Instead of thinking of a public registry where anyone can submit anything as a weakness, how can we leverage it as a strength? Can we work more with security researchers to detect patterns in malware and prevent these packages from reaching users?

Lastly, the story of trusted publishing is definitely one of good news / bad news. We successfully predicted that credential-stealing malware was an increasing threat, and we even worked on a security mitigation, although it did not get rolled out in time to prevent these destructive attacks. What attack trends are growing today that we can anticipate and start working on mitigations for, so the threat doesn't grow as large?

A huge thank you to all the people who have worked on this problem over the past year: the folks who operate package repositories, and dozens of people across Microsoft, GitHub Actions, and npm. Open source and security are team sports, and working together we can make this incredible public good available for the future.