Axios is one of the most popular packages on npm — over 100 million weekly downloads. On March 26, 2026, an attacker hijacked a maintainer account and published a version that silently installed a Remote Access Trojan on every machine that ran npm install.
The attack lasted 3.5 hours before Socket.dev detected it. With 100 million weekly downloads, even that short window was enough to hit thousands of CI/CD pipelines and developer machines worldwide.
Every Laravel project has a package.json. This isn’t just a JavaScript problem.
Table of contents
Open Table of contents
- What happened
- Why AI agents make this worse
- How to check if you’re affected
- 10 protections for your Laravel project
- 1. Pin exact versions
- 2. Commit your lockfile
- 3. Add
.npmrchardening - 4. Run npm audit in CI
- 5. Install Socket for GitHub
- 6. Manual review before adding packages
- 7. Open-source only
- 8. Isolate environment variables
- 9. Prefer remote integrations over local npm packages
- 10. Document your CVE decisions
- How Growth Method handles MCP integrations
- How these layers work together
- What I asked and learned
- The bottom line
What happened
The attacker hijacked the npm account of jasonsaayman, a maintainer of the Axios package. They published axios@1.14.1 and axios@0.30.4 — both identical to the real Axios source code, with one change: package.json now listed plain-crypto-js@4.2.1 as a dependency.
plain-crypto-js didn’t exist before the attack. It was a brand-new package created solely to deliver malware. The name sounds like a legitimate crypto utility. The code used two layers of obfuscation — reversed Base64 encoding plus XOR cipher — to hide what it actually did.
When decoded, the strings revealed C2 (command and control) server URLs, shell commands, and file paths. The postinstall script ran automatically on npm install, downloaded a RAT (Remote Access Trojan), and then deleted itself from disk to avoid detection.
Andrej Karpathy noted the broader implication: package popularity is not a proxy for security. The bigger the dependency, the more attractive the target.
StepSecurity published a detailed writeup of the attack mechanics and timeline.
Why AI agents make this worse
When an AI coding agent runs npm install — Claude Code, Codex, Cursor — the blast radius includes everything the agent can access:
- File system —
.envfiles, SSH keys, cloud credentials - Auth tokens — GitHub tokens, npm tokens, API keys in the environment
- Network access — the agent can make outbound requests
- Terminal access — the postinstall script runs with the same permissions as the agent
The agent sees a clean npm install success message and moves on to writing code. No human ever saw a prompt. The RAT doesn’t even need to persist — the agent’s session already has everything the attacker wants.
And AI has changed both sides of the equation:
Attacks are cheaper to produce. Generating a convincing malicious package — plausible name, well-written README, realistic source code — used to take effort. Now an attacker can spin up dozens in minutes. plain-crypto-js is a perfect example.
Targets are less vigilant. When an agent suggests a package, most people hit accept. The friction that used to exist — manually typing a name, scanning the npm page, checking download counts — is gone. As one commenter put it:
This is why AI-assisted dependency auditing needs to be standard in every CI pipeline. 300M weekly downloads means millions of attack surfaces. The npm ecosystem trusts too much by default.
How to check if you’re affected
Pushpak from the Laravel community shared a prompt for scanning your machine. Here’s an improved version that also checks for the malicious dependency plain-crypto-js — a package that has no legitimate use and exists solely as a RAT dropper:
There's an active supply chain attack on axios. The compromised versions
are axios@1.14.1, plain-crypto-js@4.2.1 and axios@0.30.4. DO NOT upgrade
or install any dependencies.
Scan my entire machine for lock files and check if any project has these
compromised versions resolved.
Step 1: Find all lock files
find ~ -maxdepth 6 \( -name "package-lock.json" -o -name "yarn.lock" \
-o -name "pnpm-lock.yaml" -o -name "bun.lock" \) 2>/dev/null \
| grep -v node_modules | grep -v .cache | grep -v .Trash | grep -v Library
Step 2: Search those lock files for the compromised versions AND the
malicious dependency
xargs grep -l "axios.*1\.14\.1\|axios.*0\.30\.4\|plain-crypto-js"
Note: The presence of plain-crypto-js anywhere in a lockfile is a direct
indicator of compromise. Flag it as AFFECTED immediately regardless of
which package pulled it in.
Step 3: Also check all package.json files for axios dependency declarations
using ^ ranges that could resolve to the compromised versions on next
install (e.g. ^1.14.0, ^1.13.x, ^1.x). List these as "at risk" projects.
Report:
- AFFECTED: projects with compromised versions OR plain-crypto-js in lock files
- AT RISK: projects with caret ranges that could resolve to 1.14.1 on next install
- CLEAN: if nothing found
Do not install, update, or modify anything. Read-only scan only.
10 protections for your Laravel project
Here’s the full defence stack I put in place for Growth Method, and what each layer catches:
1. Pin exact versions
Remove ^ and ~ from package.json. Caret ranges are how the compromised version would silently arrive — ^1.13.0 resolves to 1.14.1 on next install without anyone approving it.
{
"devDependencies": {
"@tailwindcss/postcss": "4.2.1",
"laravel-vite-plugin": "2.1.0",
"vite": "7.3.0"
}
}
No ^, no ~. You control when updates happen and can review changelogs first.
2. Commit your lockfile
You probably already commit package-lock.json. This pins the entire dependency tree — not just the packages you list in package.json, but everything they depend on too. The lockfile is what stopped most Laravel projects from being affected, even if they had Axios with a ^ range.
3. Add .npmrc hardening
Create a .npmrc file in your project root:
# Block postinstall scripts — the exact attack vector
ignore-scripts=true
# Don't install packages published less than 7 days ago
min-release-age=7d
ignore-scripts=true is the single most impactful change. The Axios attack relied entirely on a postinstall script to execute the RAT. With scripts disabled, the malicious code never runs — even if the compromised package is in your node_modules.
min-release-age=7d adds a waiting period. Socket detected and npm removed the Axios attack within hours — a 7-day delay means you’d never have pulled it.
4. Run npm audit in CI
Add a GitHub Actions workflow that runs npm audit on every pull request:
# .github/workflows/npm-audit.yml
name: NPM Security Audit
on: [pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm audit --audit-level=moderate
This catches known CVEs before they reach the codebase. It wouldn’t have caught the Axios zero-day (no CVE existed yet), but it’s a free 10-line workflow that covers the common case.
5. Install Socket for GitHub
Socket does what npm audit can’t — behavioural analysis. It detects obfuscated code, suspicious network calls, filesystem writes, maintainer account changes, and 70+ other risk signals. Socket is what detected plain-crypto-js within 6 minutes.
Socket is now integrated directly into npmjs.com — when you check any package page, you’re already seeing Socket’s analysis. The GitHub App automates the same checks on your pull requests.
It’s free for open-source. Install it at socket.dev/features/github.
6. Manual review before adding packages
Automated tools aren’t infallible. In a previous session, Socket gave a 76/100 score to a package that was 1.8MB of obfuscated _0x416039 gibberish with a single anonymous maintainer and no linked repository. Human review caught what tooling missed.
Before adding any package, check npmjs.com for:
- Maintainer — first-party vendor account with a linked GitHub org
- Downloads — stable or growing, not brand new with inflated counts
- Last publish — recent but not suspicious (published today could mean compromised)
- Open issues — active triage, not hundreds of ignored reports
- Source code — readable, not obfuscated
- License — standard open-source (MIT, Apache)
7. Open-source only
Decline obfuscated code on principle. Tools like javascript-obfuscator produce output that is intentionally unreadable — and it’s the same technique the Axios attacker used to hide C2 server URLs and shell commands. If you can’t read it, you can’t trust it.
8. Isolate environment variables
If you run MCP servers or other Node subprocesses, only pass the credentials they need. Laravel’s StdioTransport env config replaces the process environment entirely — the subprocess can’t see your APP_KEY, database credentials, or other secrets unless you explicitly pass them.
9. Prefer remote integrations over local npm packages
Remote HTTP MCP servers eliminate the npm supply chain from the equation entirely. No package to compromise, no postinstall script to exploit, no version range to hijack. Local stdio servers are the fallback for vendors that don’t offer a hosted option.
10. Document your CVE decisions
Joel Clermont wrote about this for composer audit, and the same applies to npm. If you know about a vulnerability and choose not to patch, record why.
This is about to become more than good practice. Under the EU’s Cyber Resilience Act (CRA), internal documentation of existing CVEs will be mandatory if you choose not to remediate them. Compliance deadlines begin later this year for anyone selling software to European customers.
How Growth Method handles MCP integrations
Growth Method uses MCP (Model Context Protocol) servers to connect with third-party tools like Bento, Microsoft Clarity, GitHub, and Webflow. Each integration is an npm package running as a subprocess. This is exactly the kind of setup the Axios attack targets — so we built supply chain security into the integration architecture.
Users never run npm install. We pre-install every MCP package and ship it with the app. Users just enter their API credentials and connect. There’s no moment where npm install resolves a ^ range to a malicious version, because users never interact with npm.
Every package is vetted before it enters the registry. We only use official first-party MCP servers from the vendor themselves (Bento’s team, Microsoft’s team, Webflow’s team). Each one is open-source, readable, and published under a standard license. We check the maintainer, the source code, and the Socket.dev analysis before approving it.
Source transparency is built into the UI. Every integration’s edit panel includes links to the package’s GitHub repo, npm registry page, and Socket.dev security analysis. Users can verify for themselves what’s running.
Subprocesses are sandboxed. When Growth Method launches an MCP server, it replaces the process environment entirely. The subprocess only sees the credentials you explicitly pass — it can’t access your APP_KEY, database connection, or any other secret.
Remote servers are preferred over local packages. When a vendor offers a remote HTTP MCP server, we use that instead. No npm package on the server, no supply chain risk. Local stdio is the fallback for vendors that don’t offer a hosted option.
Even with all of this, the risk isn’t zero. A vetted package could have a compromised transitive dependency, or a future update could introduce something malicious. That’s why the protections are layered — pinned versions, ignore-scripts, min-release-age, and Socket for GitHub all apply on top of the vetting process.
How these layers work together
No single protection catches everything. The point is that an attacker needs to bypass all of them simultaneously.
| Layer | What it catches |
|---|---|
| Pinned versions | Automatic upgrades to compromised versions |
| Lockfile committed | Pins the full transitive dependency tree |
ignore-scripts=true | Any postinstall-based attack |
min-release-age=7d | Brand-new malicious versions |
| npm audit in CI | Known CVEs on pull requests |
| Socket for GitHub | Zero-day malicious packages, obfuscated code, account hijacks |
| Manual review | Suspicious metadata tooling misses |
| Open-source only | Obfuscated packages rejected on principle |
| Env isolation | Limits blast radius if a package is compromised |
| Remote-first integrations | Removes npm from the equation entirely |
The Axios attack would have been stopped by any one of the first four layers. That’s the whole point of defence in depth — you don’t need every layer to catch every attack. You need at least one layer to catch each attack.
What I asked and learned
Explain
.npmrc— do I already have one?
I didn’t. Most Laravel projects don’t. Creating one with ignore-scripts=true and min-release-age=7d took 30 seconds and blocks the exact attack vector used in the Axios compromise.
What about Socket.dev?
Socket is a service you set up outside the codebase. The GitHub App is free, takes 30 seconds to install, and automatically reviews every PR that adds or changes a dependency. It’s the tool that caught plain-crypto-js within 6 minutes.
These APIs rarely change — Bento’s REST API and Clarity’s API are stable. New MCP tools usually just expose existing API endpoints that were already available.
This is another argument for pinning and not rushing to upgrade. MCP servers are thin wrappers around stable REST APIs. A week-old version works just as well as today’s. For stable API wrappers, “latest” is a risk, not a feature.
The bottom line
Dependency auditing needs to be treated like patching, not an afterthought. The Axios attack showed that a single compromised npm account can turn the most trusted package on the registry into a malware dropper — and that AI agents make the blast radius larger than ever.
The good news: the protections are straightforward. Pin your versions, commit your lockfile, add an .npmrc, and install Socket. None of it is hard. The hard part is doing it before you need to.