# Supply chain attacks on npm and PyPI: what your team should do

URL: https://brainpowergroup.eu/en/blog/supply-chain-attacks-on-npm-and-pypi
Date: 2026-09-03 (updated 2026-09-23)
By: Milos, Brainpower Group

In a supply chain attack on npm or PyPI, malicious code often runs during installation. Lockfiles with hashes and a waiting period for new versions limit the risk.

In a supply chain attack on npm or PyPI, the attacker takes over a package your team already trusts and publishes a new version with malicious code in it. That code often runs during installation, on a developer's laptop or in CI, which is exactly where the tokens and cloud keys are. Most of your protection comes from team rules: lockfiles with hashes, a waiting period for new versions, publishing without long-lived tokens and a review of every new dependency.

## How these attacks happen

The way in differs per incident, and each entry point needs a different control.

| Incident | When | How the attacker got in |
|---|---|---|
| event-stream (npm) | 2018 | Offered to maintain the package, got control and added a dependency aimed at the Copay wallet |
| ua-parser-js (npm) | October 2021 | Took over the maintainer's npm account and published three versions with a cryptominer and a password stealer |
| xz utils | March 2024 | Contributed for over two years, gained commit access and hid a backdoor in 5.6.0 and 5.6.1 (CVE-2024-3094) |
| Shai-Hulud (npm) | September 2025 | A self-replicating worm spreading through hijacked maintainer accounts; GitHub removed more than 500 packages |
| LiteLLM and telnyx (PyPI) | March 2026 | A publishing token leaked through a compromised Trivy dependency in the build |
| ChainDrop (npm) | August 2026 | Stolen maintainer credentials, followed by more than 400 compromised packages |

xz utils is not on npm or PyPI, but it shows how patient an attacker can be.

## What the 2025 and 2026 worms changed

Shai-Hulud took over packages automatically in September 2025. GitHub describes it as a self-replicating worm that got in through hijacked maintainer accounts, injected post-install scripts into popular packages and could steal more kinds of secrets than npm tokens.

According to Microsoft, ChainDrop worked in a similar way in August 2026. The malware collected npm, GitHub and cloud credentials from developer laptops and CI runners. With stolen publishing rights, it released new patch versions of other packages the same maintainer could access. Some of those went to npm through the project's normal GitHub Actions workflow, so they carried valid provenance.

Provenance tells you which pipeline built a package, but not who was driving that pipeline at the time. Treat it as one signal and keep checking what you pull in.

## Lockfiles and a waiting period for new versions

Commit your lockfile and have CI install only what is in it, with `npm ci` or `pnpm install --frozen-lockfile`. In Python, use a lockfile with hashes, for example through `uv lock` or `pip-compile --generate-hashes`. As PyPI points out, `pip freeze` does not produce a lockfile.

A lockfile only helps as long as nobody accepts an update without looking, so the lockfile diff belongs in code review.

A waiting period helps as well. According to PyPI, the compromised LiteLLM versions were online for 2 hours and 32 minutes and were downloaded more than 119,000 times in that window. For telnyx it was 3 hours and 42 minutes. A team that only installed versions a few days old would never have pulled them in.

npm has a `min-release-age` setting for this, and uv and pip can do the same with a relative date. I would start with three to seven days, plus an agreed fast lane for security updates.

## Install scripts and the developer's laptop

With ua-parser-js, Shai-Hulud and LiteLLM, the malicious code ran during installation, and a developer laptop holds GitHub tokens, cloud credentials and SSH keys. Turn off dependency install scripts by default wherever your package manager allows it, and allow them only for packages that need them.

Our developers work on managed, encrypted laptops with centrally managed accounts and SSO with two-factor authentication. Even then, a token in a `.env` file can leak, so keep secrets short-lived and off the laptop. How to do that is covered in [managing secrets and API keys safely](/en/blog/managing-secrets-and-api-keys-safely).

## Publishing without long-lived tokens

If you publish internal packages, or your CI writes to a registry with a token, that token is exactly what these worms look for.

After Shai-Hulud, npm announced that classic tokens would go and that publishing would only be possible with two-factor authentication, short-lived tokens or trusted publishing. With trusted publishing, your CI workflow gets a short-lived token for each publish and you store no secret anywhere. PyPI recommends the same, along with two-factor authentication on every account and preferably a hardware key.

ChainDrop shows where that stops helping: with a maintainer's GitHub account, an attacker can run the official workflow. So require a second person to approve releases, for example with a protected environment. Pin GitHub Actions to a commit SHA rather than a tag, because a tag can be moved.

## Review new dependencies like you review code

Whenever someone adds a dependency, answer a few questions in the pull request. Why this package? Who maintains it and how long has it existed? Does it have install scripts? Have a person review update PRs from Dependabot or Renovate as well, instead of merging them automatically.

Our developers use Claude Code every day, and a tool like that can suggest a package nobody on the team knows. Check that it exists and that it is the one you mean before it lands in your lockfile. More on team rules for these tools is in [AI coding tools in a remote team](/en/blog/ai-coding-tools-in-a-remote-team).

## When it happens anyway

Treat every laptop or runner that installed a compromised version as compromised. For ChainDrop, Microsoft advised revoking and rotating credentials from a clean machine, checking lockfiles, caches and artifact repositories, and rebuilding images from a known good baseline. That advice fits every incident in the table.

Write down in advance which tokens exist and who can revoke them. For lessons from other well-known incidents, see [lessons from well-known hacks](/en/blog/lessons-from-well-known-hacks).

## Sources

- [npm: Details about the event-stream incident](https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident)
- [CERT-EU: Security Advisory 2021-057 on UA-Parser-JS](https://www.cert.europa.eu/publications/security-advisories/2021-057/)
- [Russ Cox: Timeline of the xz open source attack](https://research.swtch.com/xz-timeline)
- [GitHub: Our plan for a more secure npm supply chain](https://github.blog/security/supply-chain-security/our-plan-for-a-more-secure-npm-supply-chain/)
- [PyPI: Incident report on the LiteLLM/Telnyx supply-chain attacks](https://blog.pypi.org/posts/2026-04-02-incident-report-litellm-telnyx-supply-chain-attack/)
- [Microsoft: ChainDrop supply chain compromise](https://www.microsoft.com/en-us/security/blog/2026/08/04/chaindrop-supply-chain-compromise-anatomy-self-propagating-worm/)

## Frequently asked questions

**What is a supply chain attack on npm or PyPI?**

An attacker gets malicious code into a package you use as a dependency, usually through a hijacked maintainer account or a compromised build pipeline. The code often runs at install time, on a developer's laptop or in your CI.

**Does a lockfile protect you against a compromised package?**

Partly. A lockfile with hashes makes sure you install exactly the version you checked before. It does not help when someone accepts an update without looking, so the lockfile diff belongs in code review.

**What should you do if your team installed a compromised version?**

Treat the laptop or runner as compromised. From a clean machine, revoke and replace every token and key that was on it, remove the version from lockfiles and caches, and rebuild images from a known good baseline.

---

Source: https://brainpowergroup.eu/en/blog/supply-chain-attacks-on-npm-and-pypi
Brainpower Group, contact: https://brainpowergroup.eu/en/contact, hello@brainpowergroup.eu
