On March 31, 2026, someone hijacked the npm account of axios's lead maintainer. Axios is an HTTP client with over 100 million weekly downloads. The attacker skipped the project's CI pipeline and published two malicious versions directly from the npm CLI.
Those versions didn't touch axios's own code. They added one new dependency, plain-crypto-js, which existed only to run a postinstall script. That script contacted a command-and-control server and dropped a remote access trojan on the machine.
All of this happened during npm install, before any application code ran.
The malicious versions were live for about four hours. Any fresh install or auto-update in that window could have pulled them.
π You didn't install malware. You ran
npm install.
This post covers what happened in 2026, how these attacks work, and a checklist you can apply to your project in about 30 minutes. π
π 2026 So Far: A Quick Recap
Axios wasn't a one-off. Here are some of the year's major incidents:
| Date | Target | How |
|---|---|---|
| Mar 31 | axios |
Maintainer account hijacked; malicious dependency with a postinstall script |
| Apr 22 | @bitwarden/cli |
Preinstall script downloaded the Bun runtime and ran a credential stealer |
| Apr 29 | SAP CAP packages (@cap-js/*, mbt) |
"Mini Shai-Hulud" worm, preinstall hook |
| May 11 |
@tanstack/* and others |
Abused a pull_request_target workflow and poisoned the GitHub Actions cache to steal an OIDC token; published with valid provenance |
| May 19 | @antv/* |
Compromised maintainer account; ~639 malicious versions across 323 packages in about an hour |
| Jun 1 | @redhat-cloud-services/* |
Compromised employee GitHub account pushed commits that bypassed code review |
| Jun 3 | 57+ packages |
binding.gyp campaign; 286+ malicious versions in under two hours |
| Jul 14 | AsyncAPI packages | Commits pushed to unprotected pre-release branches; CI exposed NPM_TOKEN
|
π The pattern is clear: attacks are faster, automated, and increasingly target the CI pipeline instead of just individual developers.
π§ How These Attacks Actually Work
Most incidents combine a few of these techniques.
1. π Account Takeover
A maintainer gets phished or has a token leaked, and the attacker publishes a "new version" of a trusted package.
Your ^1.2.0 range happily accepts it.
2. π Install Scripts
npm runs preinstall, install, and postinstall scripts automatically.
That means arbitrary code on your machine or CI runner, with access to your environment variables, SSH keys, and cloud credentials.
3. πͺ± Self-Replication (Worms)
The Shai-Hulud family steals npm tokens and GitHub tokens, then uses them to publish infected versions of other packages the victim maintains.
One compromised developer becomes dozens of compromised packages.
4. π CI/CD Compromise
The TanStack attack needed no stolen password.
The attacker abused a GitHub Actions workflow to extract a publishing token from the runner. Because the package was built by the real pipeline, it carried valid provenance.
5. π Typosquatting and Dependency Confusion
Look-alike package names (reacct-dom), or a public package with the same name as your private one.
π΅οΈ What Do Attackers Steal?
- π npm and GitHub tokens
- βοΈ Cloud credentials (AWS, GCP, Azure)
- βΈοΈ Kubernetes tokens
- ποΈ SSH keys
- βοΈ CI/CD secrets
- π
.envcontents - π Password manager data
- π€ AI tool configuration files
Some of the 2026 attack waves targeted credentials and configuration data beyond traditional development secrets.
π The Checklist
Here are practical steps you can take to reduce your exposure.
1. π» Local Development
π Commit Your Lockfile, and Review It
package-lock.json (or pnpm-lock.yaml / yarn.lock) pins exact versions of your whole dependency tree.
Treat lockfile changes in a PR as code changes.
A new, unfamiliar transitive dependency (like plain-crypto-js) is a red flag. π©
π« Turn Off Install Scripts by Default
Most attacks this year relied on preinstall or postinstall.
Disable them:
.npmrc
ignore-scripts=true
Some packages genuinely need build scripts (native modules like sharp or bcrypt).
Run their scripts explicitly after install:
npm rebuild sharp --ignore-scripts=false
If you use pnpm, it already doesn't run dependency build scripts by default (since v10). Approve only the ones you trust with:
pnpm approve-builds
Bun also skips lifecycle scripts unless a package is in trustedDependencies.
β³ Add a Release Cooldown
Malicious versions are usually caught within hours.
The axios versions were flagged about six minutes after publishing and contained within roughly four hours.
If your package manager refuses versions newer than a day or two, you skip most of that window.
All major package managers now support this, but each uses a different unit:
π¦ npm 11.10+ (.npmrc) β value in DAYS
min-release-age=2
π¦ pnpm 10.16+ (pnpm-workspace.yaml) β value in MINUTES
# pnpm 11 defaults to 1440 (24 hours)
minimumReleaseAge: 2880
π¦ Yarn 4.10+ (.yarnrc.yml) β value in MINUTES
npmMinimalAgeGate: 2880
π¦ Bun (bunfig.toml) β value in SECONDS
[install]
minimumReleaseAge = 172800
Need an urgent hotfix? π₯
Override for one install, e.g.:
npm i --min-release-age 0 package@version
π― Be Deliberate With Version Ranges
^ and ~ let new versions in automatically.
For critical or high-privilege dependencies, consider exact versions and upgrade on purpose.
2. π Auditing
π©Ί Run npm audit Regularly
It catches known vulnerabilities, not fresh malware, but it's still a baseline.
βοΈ Verify Signatures and Provenance
npm audit signatures
This checks registry signatures and provenance attestations for your installed packages.
The axios malicious releases were missing the GitHub Actions provenance that legitimate releases had, which was a key signal.
π¨ But don't treat provenance as proof of safety.
The TanStack and Red Hat packages had valid provenance because the attackers compromised the real build pipeline.
π‘ Add a Dependency Scanner
Tools like Socket, Snyk, or GitHub's Dependabot alerts watch for suspicious behavior (new install scripts, network calls, obfuscated code) and known-malicious packages.
Socket flagged the axios release within minutes.
π€ Make Your Update Bots Wait Too
Dependabot and Renovate can open PRs, and trigger CI installs, before a human looks.
Give them a cooldown:
.github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 2
renovate.json
{
"minimumReleaseAge": "2 days"
}
Both still let security updates through without the delay.
3. ποΈ CI/CD Security
π§ Use Deterministic Installs
Use the appropriate install command for your package manager:
npm ci # npm
pnpm install --frozen-lockfile # pnpm
yarn install --immutable # yarn
These fail if the lockfile and package.json disagree, instead of silently resolving new versions.
π Keep Secrets Away From the Install Step
Don't expose deploy keys or cloud credentials as environment variables during npm ci.
Inject them only into the steps that need them.
β±οΈ Use Short-Lived, Least-Privilege Tokens
Avoid long-lived npm tokens in CI.
If you publish packages, use npm Trusted Publishing (OIDC) and enable 2FA on your npm account.
π§± Harden GitHub Actions
- β‘ Be very careful with
pull_request_target. It runs with your repo's secrets, and checking out PR code inside it is how the TanStack attack started. - π Pin third-party actions to a full commit SHA, not a tag.
- πΏ Protect all branches that can trigger publishing, including
nextor pre-release branches (the AsyncAPI attack used one).
π Restrict Outbound Network Access
Restrict outbound network access from CI runners where you can, so a payload can't reach its command-and-control server.
4. π Have a Response Plan
If a package you use is compromised:
- π Identify exposure. Check whether any lockfile in your projects resolved to an affected version, and when your machines or CI ran an install.
- π§― Isolate before rotating. Some 2026 payloads (the TanStack wave) installed a background service that deletes your home directory if it detects the stolen GitHub token was revoked. Clean or reimage affected machines first, then rotate.
- π Rotate everything the install could see: npm tokens, GitHub tokens, cloud keys, SSH keys, and CI/CD secrets.
- π§Ή Rebuild from a clean lockfile pinned to a known-good version.
- π£ Hunt for unexpected repos, workflows, or package versions published from your accounts.
π§° Putting It Together: A Hardened Starter Setup
Here's a minimal setup you can drop into a project today.
βοΈ .npmrc
ignore-scripts=true
min-release-age=2
save-exact=true
π .github/workflows/ci.yml (Relevant Parts)
name: CI
on:
pull_request: # not pull_request_target
push:
branches: [main]
permissions:
contents: read # least privilege by default
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<full-commit-sha>
- uses: actions/setup-node@<full-commit-sha>
with:
node-version: 22
cache: npm
- name: Install (no secrets in this step)
run: npm ci
- name: Verify signatures
run: npm audit signatures
- name: Rebuild trusted native deps
run: npm rebuild sharp --ignore-scripts=false
- name: Build & test
run: npm run build && npm test
It isn't bulletproof. Nothing is.
But it closes the paths that most 2026 attacks actually used: install scripts, brand-new malicious versions, silent version drift, and over-privileged CI. πͺ
π Key Takeaways
- πͺ Install scripts are the #1 entry point. Disable them by default and allowlist the few you need.
- β Time is your cheapest defense. A 1β2 day cooldown would have skipped most of this year's malicious releases.
- π Lockfiles plus
npm cistop silent changes to your dependency tree. - π§Ύ Provenance helps, but it isn't proof. Compromised pipelines can produce validly signed malware.
- π― Your CI is a target. Least-privilege tokens, careful workflow triggers, and SHA-pinned actions matter.
The next time you run npm install, remember: you're not just installing code. You're trusting every package in your dependency tree to execute safely.
π‘οΈ Secure your dependencies before attackers turn them into your weakest link.
Top comments (0)