Code Repositories – Trust but Verify
Why npm Supply Chain Attacks Concerns Us All
Over the past year the npm ecosystem has shifted from nuisance to genuine threat and it’s worth dissecting this in detail because it makes a lot of difference for you as an engineer and what you need to pay close attention to regarding your development practices.
Npm - the default package manager for the Node.js runtime environment - has seen a lot of abuse in the past couple of years and gone from a place where threat-actors first planted cryptominers for unsuspecting devs, to one where they plant self-replicating worms, credential stealers, and destructive payloads. Since September 2025 we have witnessed the Shai-Hulud worm and its “close relatives” (Mini Shai-Hulud, and Miasma at Red Hat), and notable targeted compromises of Axios, node-ipc, and Mastra, two of which are tied to a North Korean state actor. The common thread is simple: you do not have to publish anything to be hit. Running npm install is enough, because the malicious code arrives through ordinary dependency resolution on your laptop or a CI runner, with no exploit required.
Every incident below reached its victims through ordinary dependency resolution, on developer laptops and CI runners, with no exploit and no interaction beyond a routine install. This is not a maintainer problem that happens to other people. It is our problem the moment we resolve a dependency.
The campaigns we have seen since September 2025 are built for credential theft, espionage, and in several cases outright destruction, and a number of them were self-replicating once they landed. Shelf-meters of articles have been written about Shai-Hulud but what I want to talk about is why the npm leverage is so powerful, and the specific things you and your dev teams should do to reduce our exposure when you build with shared repositories and pull from public registries.
Remember, there is never one thing that saves us, it is a defense-in-depth strategy that does: multiple layered defenses, practices and processes working together.
The chef and the secret sauce
Let me start with an analogy to set the scene. A skilled chef makes an authentic tomato sauce from raw ingredients he buys himself: tomatoes, garlic, basil, using his own oil and salt. He controls every input, so he has complete visibility into what goes into it. The sauce is very good, good enough that he bottles it and sells it to other restaurants.
Chef later discover that a specific herb blend significantly improves his sauce that only uses basil . But the restaurant business is intense so he calls on his friend, a trusted farmer, to prepare and package the exact herb blend for him. With his new ‘Chef’s Special Sauce’, the chef now sells three things: his original sauce, made entirely in house; the packaged herb blend from the farmer; and the new Chef Special Sauce that uses the herb blend as a core ingredient.
Weeks later, a disgruntled farm worker, fired by the farmer and on his way out, secretly stirs crushed chilli into the herb blend. Thousands of contaminated packets ship to the chef. He sells the tainted herbs directly, and he also brews a large batch of the special sauce with them. When the unexpected heat shows up in chef’s own cooking he warns the herb buyers immediately. A week later the complaints arrive from restaurants that bought Chef’s Special Sauce, because the contamination silently had made its way into chef’s special sauce through the dependency on the farmer’s herb blend, and other restaurants now used chef’s special sauce as tomato base in their stews.
That is the whole supply chain attack, in one kitchen.
The original sauce is your proprietary code. You control the inputs and you can see what is in it. The herb blend is a direct dependency, an open-source package you pull in to save time, supplied by a maintainer you assume is reliable. The special sauce is a transitive dependency; the package you never chose that arrived underneath one you did.
In npm a single install often pulls in dozens of packages beneath it, the chain can be long and you inherit every risk in that tree. We call that transitive risk, and it is the heart of the problem. Classic supply chain risk.
The disgruntled worker here is the threat actor, poisoning a trusted component because he knows it will be distributed automatically to everyone downstream without validation. The crushed chilli is the malware. The master recipe, the strict list of exactly which dishes use the herb blend, is your Software Bill of Materials. The moment the chef learns the blend is compromised, a good SBOM lets him query every recipe that depends on it and pull those dishes immediately, rather than waiting for customers to complain. And the taste test, inspecting every new batch before it goes in the pot, is your continuous scanning. The first batch of herbs was fine. The contamination happened later, independently. A package that was clean when you adopted it can be hijacked months afterward, so safe last week does not mean safe today.
The Shai-Hulud lineage
The most instructive campaigns of the past year share a single bloodline. Shai-Hulud, named after the sandworms in Dune, was the first self-replicating worm to operate at scale inside npm.
The original wave landed in September 2025. It began with phishing aimed at package maintainers, then ran a malicious postinstall script that used the TruffleHog secret scanner to harvest GitHub, npm, AWS, and GCP credentials from the environment. It published stolen secrets into public GitHub repositories named “Shai-Hulud,” then used the victim’s npm token to automatically backdoor other packages they maintained, spreading without anyone at the keyboard [See below for more: Wiz, Unit 42, CISA].
The second wave, marked “Sha1-Hulud: The Second Coming,” surged on 24 November 2025 and was a meaningful escalation. It moved execution from postinstall to preinstall, so the payload runs before installation even completes and before most checks. It installed the Bun runtime to sidestep standard Node.js monitoring, ran an obfuscated payload, and crucially added a destructive fallback: if it could not establish an exfiltration channel, it would attempt to wipe the victim’s home directory. Datadog counted roughly 796 packages and 1,092 versions; Unit 42 put the GitHub blast radius far higher. Zapier, PostHog, and Postman were among those caught – to mention but a few [See below for more: Datadog, Unit 42, Microsoft, Elastic].
In May 2026 we saw Mini Shai-Hulud, and the entry point here is what we need to focus on. Every prior wave of the Shai-Hulud family started with a stolen credential. This one was different and did not. Rather, the attacker exploited a pull-request workflow misconfiguration in TanStack’s GitHub Actions CI, poisoned the build cache from a fork, and then they just waited.
Roughly eight hours later a legitimate maintainer merge triggered the normal release workflow, which pulled the poisoned cache and ran the attacker’s code, scraped all his tokens straight from the runner’s memory, and exchanged them for npm publish credentials. It spread from TanStack to packages linked to Mistral AI, UiPath, and OpenSearch, and notably became the first campaign to hit npm and PyPI in a single coordinated operation. Its persistence even survived npm uninstall by hooking into Claude Code sessions and VS Code tasks. When the worm’s full source was published openly on 12 May, the technique became available to anyone [See below for more: Akamai, Microsoft].
That open-sourcing is why Miasma matters. On 1 June 2026 an attacker pushed orphan commits, bypassing code review, into Red Hat repositories using a compromised employee GitHub account, then published 96 malicious versions across 32 packages in the @redhat-cloud-services namespace. The payload itself was a lightly reskinned Mini Shai-Hulud, with the Dune references swapped for Greek mythology and repositories tagged “Miasma: The Spreading Blight.” It added new collectors that could enumerate every GCP and Azure identity a host can reach which was a clear shift toward the cloud control plane rather than just static secrets previously targeted.
Two details are worth carrying into any internal discussion.
First, the credentials used to publish had been sitting in infostealer logs for roughly seven weeks before they were weaponised, which is a monitoring failure as much as anything. But that’s what threat actors do – they exploit human weaknesses and lack of rigour.
Second, Red Hat’s own advisory confirmed that no Hybrid Cloud Console release shipped during the window and that their publishing process strips install-time scripts, so the risk was exclusive to downstream consumers of the npm packages and not the Console itself [See below for more: Wiz, Snyk, CybelAngel, Red Hat RHSB-2026-006].
Beyond the worms
Three more incidents round out the picture, and each teaches something the worm campaigns do not.
Axios, March 2026. Axios is one of the most depended-on libraries in the ecosystem, with well over 100 million weekly downloads. The maintainer account was compromised through patient and very targeted social engineering attack, including impersonated colleagues and fake Slack and Teams meetings. Which goes to show the extent to which threat actors are willing to go and arguably a whole different discussion worth having one day.
The attacker changed the account email, bypassed the project’s GitHub Actions OIDC publishing pipeline entirely, and published two poisoned versions directly with a long-lived token. Those versions pulled a fake dependency, plain-crypto-js, which had been pre-staged with a clean release eighteen hours earlier to build a credible history (!). Its postinstall dropper deployed a cross-platform remote access trojan to Windows, macOS, and Linux, then deleted itself to frustrate forensics. The packages were live for about three hours before they was detected and removed. Microsoft’s and Google’s threat analysis teams attributed it to a North Korean state actor, tracked as Sapphire Sleet and UNC1069 respectively [See below for more: Huntress, Google GTIG, Microsoft, Tenable].
The takeaway: account MFA did not help here because the attacker published with a stolen token, but a clean lockfile would have.
Mastra, June 2026. The attacker hijacked a dormant former-contributor account whose publish access to the @mastra scope had never been revoked, then mass-published more than 140 packages in an 88-minute window. The library code was untouched. Each release simply added one line to its dependency list pointing at easy-day-js, a typosquat of the dayjs date library crafted to survive a casual glance. A clean decoy of easy-day-js went up the day before; the armed version followed, and the caret range resolved installs straight to it. The postinstall dropper here pulled a cross-platform infostealer that also targeted cryptocurrency wallets. Microsoft attributed this exploit to Sapphire Sleet as well, the same cluster behind Axios, and the tradecraft is nearly identical: a clean-then-armed typosquatted dependency, an install-time dropper that self-deletes. Two structural failures combined here, neither a vulnerability in the classic sense: npm does not expire scope access on inactivity, and Mastra generated SLSA provenance on its CI builds but did not require it, so a plain token could publish without attestation. An install policy that verified signatures would have rejected the entire wave [See below for more: JFrog, StepSecurity, OX Security, Mend, The Hacker News].
node-ipc, May 2026. Our last example but also the one that should change how you read the rest of this article. Three malicious versions of node-ipc, a foundational inter-process communication library, were published after an attacker took over a dormant maintainer account, not by stealing a password but by re-registering the account’s expired email domain and walking through npm’s normal password-recovery flow. Account MFA was irrelevant because recovery does not require it. The important part is the payload. It was not in any lifecycle script; rather, the malicious code was appended to the CommonJS bundle (node-ipc.cjs) and fires whenever the package is loaded through require(”node-ipc”). It harvested credentials across more than ninety categories and exfiltrated them over DNS TXT queries to evade network monitoring (“low and slow”). This is distinct from the well-known 2022 protestware incident on the same package [See below for more: Socket, StepSecurity, Datadog, Snyk, The Hacker News].
Now what we can do about it?
The advice below is ordered roughly by impact for an engineer who consumes packages, which is all of us. Where a control has a known blind spot, it is called out, because a defence you trust blindly is worse than one you understand.
Pin dependencies and install from lockfiles.
Commit package-lock.json or yarn.lock, and in pipelines please use npm ci, not npm install, so the exact versions and hashes in the lockfile are enforced. A common miss is assuming an exact version in your own manifest protects you; the transitive packages beneath it often use flexible ranges, and the caret range is exactly what armed both the Axios and Mastra attacks. A locked tree would have stopped both for anyone who had one. We have talked about that before.
Enforce provenance, do not just generate it.
Use npm audit signatures or an install policy that requires SLSA attestations, so that unsigned or unattested versions are rejected. Mastra is the proverbial cautionary tale: provenance was produced but not required, and the malicious wave simply dropped it. Generating provenance when you don’t enforce it buys you nothing.
Disable lifecycle scripts, and know what that does not cover.
Set npm config set ignore-scripts true locally and in CI and allow scripts only for the specific packages that genuinely need a compile step. This neutralises the preinstall and postinstall execution path used by Shai-Hulud, Axios, and Mastra. It would not have stopped node-ipc, whose payload ran on require(), not on install. Treat this as necessary, not sufficient, and pair it with the network control below.
Filter network egress from build environments.
Block outbound traffic from CI runners and build containers by default and allow-list only the registry and your deployment endpoints. Almost every payload here needed outbound access to send credentials home, and node-ipc’s DNS-based exfiltration is exactly why you also want to restrict DNS to approved resolvers. This is the backstop that catches what the earlier controls miss, including the require-time payloads.
Adopt a dependency cooldown.
Configure automation so new versions are not pulled until they have been public for a set window. Seven days is the floor we adopted after Axios; Elastic moved to fourteen days, and we should match that. Most of these malicious versions were detected and removed within hours, so a cooldown would have meant your pipelines never saw them.
Scope and rotate tokens, and prefer trusted publishing.
Generate registry and repo tokens with the narrowest scope, make automation tokens read-only unless they must publish (principle of least privileges – always), and use short-lived OIDC-based trusted publishing instead of static secrets. After the September wave, GitHub made 2FA mandatory for local publishing, capped token lifetimes at seven days, and promoted trusted publishing, an approach pioneered by PyPI that removes long-lived tokens from pipelines. Scoping is what breaks a worm’s lateral movement when it does harvest a token.
Use phishing-resistant MFA and watch the recovery path.
We should consider rolling out platform authenticators across GitHub, npm, and cloud accounts (ideally Roll out FIDO2) and avoid SMS or basic TOTP, which reverse-proxy phishing kits today easily intercept. The detail most people forget is that node-ipc and Mastra never had to defeat MFA, they simply went around it by using expired-domain recovery and an un-revoked dormant account. So also audit account-recovery email domains and revoke scope access the moment a contributor goes inactive. I keep mentioning account management hygiene.
Audit the full dependency tree continuously.
Finally, wire software composition analysis into your pull requests and fail builds on known-malicious signatures, and keep an SBOM you can actually query. SBOM and documentation are two things we need to get much better at doing. These serve as great examples for why.
Signature-based scanners have a real gap, a brand-new worm will not match anything until researchers index it, which is why this sits alongside the controls above rather than replacing them. Its real value is speed of response: when intelligence updates, you can find every affected artifact in minutes instead of guessing.Prefer Native Over Dependency:
Evaluate if native APIs (like the built-in fetch() in Node.js v18 and above) can replace third-party libraries. Every dependency we remove is one less door for an attacker.
But remember, this is not only an npm story
The same logic applies to PyPI and not just theoretically. Mini Shai-Hulud was the first campaign to span both registries in one operation, the TeamPCP actor behind several of these waves also poisoned PyPI packages, and trusted publishing, one of our better defences, originated on PyPI in the first place. The registries differ but as evidence shows, the attacker economics don’t. Lockfiles and hash pinning, disabling install hooks, egress filtering, provenance enforcement, and cooldowns all have direct equivalents in pip and Poetry, and the chef in the story never needed to know whether his herbs came from npm or PyPI to get burned by them.
Food for thought.
Sources and further reading
Shai-Hulud (original and 2.0):
Wiz: https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack
Unit 42: https://unit42.paloaltonetworks.com/npm-supply-chain-attack/
Datadog Security Labs: https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/
Microsoft Security: https://www.microsoft.com/en-us/security/blog/2025/12/09/shai-hulud-2-0-guidance-for-detecting-investigating-and-defending-against-the-supply-chain-attack/
Elastic: https://www.elastic.co/blog/shai-hulud-worm-2-0-updated-response
Mini Shai-Hulud (TanStack):
Axios Attack:
Huntress: https://www.huntress.com/blog/supply-chain-compromise-axios-npm-package
Google Threat Intelligence Group: https://cloud.google.com/blog/topics/threat-intelligence/north-korea-threat-actor-targets-axios-npm-package
Microsoft Security: https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/
Miasma (Red Hat) Attack:
Wiz: https://www.wiz.io/blog/miasma-supply-chain-attack-targeting-redhat-npm-packages
Snyk: https://snyk.io/blog/miasma-supply-chain-attack-malicious-code-redhat-cloud-services-npm-packages/
CybelAngel (the seven-week credential trail): https://cybelangel.com/blog/miasma-supply-chain-attack-the-seven-week-credential-trail/
Red Hat advisory RHSB-2026-006: https://access.redhat.com/security/vulnerabilities/RHSB-2026-006
node-ipc Attack:
Socket: https://socket.dev/blog/node-ipc-package-compromised
StepSecurity: https://www.stepsecurity.io/blog/node-ipc-npm-supply-chain-attack
Datadog Security Labs: https://securitylabs.datadoghq.com/articles/node-ipc-npm-malware-analysis/
Mastra (easy-day-js) Attack:
JFrog Security Research: https://research.jfrog.com/post/easy-day-js/
StepSecurity: https://www.stepsecurity.io/blog/mastra-npm-packages-compromised-using-easy-day-js
OX Security: https://www.ox.security/blog/easy-day-js-supply-chain-attack-hits-mastra-ai-in-npm/
The Hacker News (Sapphire Sleet attribution): https://thehackernews.com/2026/06/144-mastra-npm-packages-compromised-via.html
Github:
For your reference – Github’s security vulnerability database inclusive of CVEs and GitHub originated security advisories from the world of open-source software. https://github.com/advisories/



