groundy
security

Vercel on the Axios npm Compromise: Platform Scanning Has a Blind Spot

Vercel's Axios changelog exposes where platform defenses stop: post-publication egress blocks leave the install-time window on dev laptops and CI runners uncovered.

8 min···3 sources ↓

On March 31, 2026, the axios npm package was hijacked for roughly three hours by an attacker who compromised the primary maintainer’s account and pushed two poisoned versions that dropped a cross-platform remote access trojan during npm install. Vercel’s changelog reads as a clean incident response: detect, block the command-and-control host, unpublish, rotate. Every control it lists is post-publication and scoped to Vercel’s own build infrastructure. The poisoned package was installable on developer laptops and CI runners outside Vercel for the entire window, and the egress block does nothing about that first pull.

What Vercel actually did, and what “no systems affected” covers

Vercel’s response was three defensive steps, all inside its own environment: it blocked outbound traffic from its build infrastructure to the command-and-control host sfrclak.com, had the malicious versions unpublished from npm, and told customers to check their own supply chains.

The phrase to read carefully is “no Vercel systems were affected.” That is scoped to Vercel’s platform. The egress block protects builds that run on Vercel’s build infrastructure. It does not protect your CI runner on GitHub Actions, the laptop a developer used at 01:30 UTC, or a self-hosted runner in your own VPC. And it intercepts the second-stage callback, not the initial npm install that pulls the dropper in the first place. After takedown, the changelog repointed npm’s latest tag to the safe axios@1.14.0 and listed axios@1.14.1, axios@0.30.4, and plain-crypto-js@4.2.1 as the affected artifacts. Remediation is on the customer: search lockfiles and node_modules for plain-crypto-js, redeploy on a clean axios, and rotate API keys, database credentials, and tokens present in the build environment.

How long was the poisoned package live on the registry?

About three hours. According to the community detection guide documenting the incident, the attacker staged the campaign on March 30, publishing a clean decoy plain-crypto-js@4.2.0 at 05:57 UTC and the weaponized plain-crypto-js@4.2.1 at 23:59 UTC. The poisoned axios@1.14.1 went live at 00:21 UTC on March 31, followed by axios@0.30.4 at 01:00 UTC. npm removed both malicious versions at 03:29 UTC.

The attacker reached the maintainer account the way supply-chain attackers usually do. They compromised the npm account of jasonsaayman, Axios’s primary maintainer, likely by stealing a long-lived classic npm access token, then changed the account’s registered email to ifstap@proton.me to lock the legitimate owner out of recovery flows. With publish access in hand, the only change they made to Axios itself was a single line in package.json. Anything on a caret range like ^1.14.0 or ^0.30.0 auto-resolved to the compromised version on a fresh install during the window. Projects that pin exact versions, or that install with npm ci against a committed lockfile, were not.

How the payload hides inside a normal-looking dependency

The poisoned Axios added one dependency that Axios’s runtime never imports, so application behavior is unchanged while a postinstall hook fires during installation.

Real axios depends on three packages: follow-redirects, form-data, and proxy-from-env. The attacker added plain-crypto-js@4.2.1, which exists for one reason. When npm resolved the tree, it ran the package’s postinstall script (node setup.js), and that dropper contacted a live C2 server at sfrclak.com:8000 and pulled platform-specific second-stage payloads. The dropper then deleted itself and replaced node_modules/plain-crypto-js/package.json with a clean decoy, leaving node_modules looking normal on inspection.

Who is behind the attack?

Microsoft Threat Intelligence attributed the malicious versions and the C2 infrastructure to Sapphire Sleet, a North Korean state actor. The second-stage payload is a cross-platform remote access trojan targeting Windows, macOS, and Linux, with capabilities including remote shell execution, directory browsing, process listing, file exfiltration, and system reconnaissance, beaming to the C2 roughly every 60 seconds.

Axios is one of the most widely installed JavaScript libraries, which is why this was a critical incident. The figure is worth pinning down because it determines how many machines entered the exposure window. Microsoft’s analysis puts axios at over 70 million weekly downloads; the community detection guide cites approximately 100 million. The two sources disagree, and neither reconciles the gap, so treat the reach as “tens of millions of weekly installs” rather than a single confident number.

Where platform and registry defenses stop short

The blind spot is structural. Registry and platform controls are reactive and egress-scoped: they act after publication, and only on traffic they can see, leaving the install-time window on unmanaged endpoints uncovered.

Walk the controls against the timeline. The unpublish came at 03:29 UTC, three hours after the first malicious publish. A developer who ran npm install at 01:30 UTC on a ^1.14.0 project had already pulled the dropper and run the RAT. Vercel’s egress block to sfrclak.com would have stopped the C2 callback only if that install happened inside a Vercel build, and only the callback, not the install itself. For the laptop at the coffee shop and the self-hosted GitHub Actions runner, none of these controls were in the path at all. This is why the second-order cost of relying on registry trust alone is high: the defenses that look strongest in a vendor changelog are the ones that fire after the artifact is already public, and only inside the vendor’s perimeter.

What to actually check right now

The reliable indicators are lockfile contents, CI log timestamps overlapping the 00:21 to 03:29 UTC window on March 31, network logs showing connections to sfrclak.com:8000, and active RAT processes.

Start with the lockfile and the install logs. Grep package-lock.json or yarn.lock for axios and confirm you do not resolve to 1.14.1 or 0.30.4, then search ~/.npm/_logs/ and your cache for plain-crypto-js. Pull CI pipeline logs for the attack window and look for any npm install or npm update runs, references to plain-crypto-js, or outbound connections to sfrclak.com. On the network side, check DNS and proxy logs: on Linux, journalctl -u systemd-resolved | grep sfrclak and a scan for port 8000 will surface whether the C2 domain was ever contacted. The community guide also lists two further transitive paths to check, @shadanai/openclaw and @qqbrowser/openclaw-qbot, which shipped a vendored, tampered copy of axios@1.14.1.

Why npm audit misses it, and what to change

npm audit cannot detect this attack after the fact. The dropper self-deletes and replaces the malicious package.json with a clean decoy, so node_modules looks normal and npm audit, which inspects the current installed state against advisory databases, finds nothing to flag. The same mechanism hides post-execution payloads running outside node_modules and the network connections already made to C2. The only dependable post-incident signals are the four above: lockfile contents, CI timestamps, network logs to sfrclak.com:8000, and live RAT processes.

The hardening playbook that closes the install-time window is well-trodden and would have blocked this attack in most configurations.

  • Install with npm ci, not npm install. npm ci enforces lockfile integrity and fails if package-lock.json is out of date, preventing surprise version resolution in CI.
  • Pin exact versions. Caret and tilde ranges resolve to the latest matching version on each fresh install. 1.14.0 does not; ^1.14.0 did.
  • Disable postinstall scripts in CI where possible. This would have fully blocked the dropper. Test carefully first, because some packages need postinstall for native addons.
  • Commit and protect the lockfile. Treat package-lock.json as a security artifact. Add a CI check that runs npm ci and then git diff --exit-code package-lock.json to fail on drift.
  • Monitor build egress. Runtime monitoring like StepSecurity Harden-Runner or Falco catches unexpected outbound connections during CI even when the malicious package self-deletes before you look.
  • Kill long-lived publish tokens. The attacker walked in through a classic npm token on the maintainer account. Move to granular publish tokens with short TTLs, require 2FA on publishing accounts, and prefer trusted publishing via GitHub Actions OIDC, which needs no stored token at all.

Vercel’s changelog did its job. It told customers what happened, what to check, and what to rotate. It also, if you read the scoping closely, documents exactly where a platform’s protection runs out: at the edge of its own build infrastructure, three hours after the poisoned artifact went live, one egress block short of your laptop.

Frequently Asked Questions

How does this compare to earlier npm compromises like event-stream, coa/rc, or ua-parser-js?

Those incidents mostly delivered coinminers or narrowly-targeted credential stealers. Axios stands out because the payload was a cross-platform remote access trojan attributed to a North Korean state actor, and because the package’s reach (tens of millions of weekly installs) gave the attacker a far larger developer-machine surface than the earlier cases.

Why doesn’t disabling postinstall scripts with —ignore-scripts always work as a defense?

Many widely-used packages run postinstall scripts to fetch or compile native binaries, including esbuild, swc, sharp, bcrypt, and the TypeScript compiler’s binary distribution. A blanket —ignore-scripts flag breaks those builds, which is why most teams cannot apply it globally and instead combine scoped script allow-listing with runtime egress monitoring.

If our CI built during the window but the deployed artifact is a bundled serverless function, is the deployed code compromised?

No. The malicious code lives entirely in plain-crypto-js, which Axios’s runtime never imports, so bundlers and tree-shakers drop it from the final artifact. The victim is the build machine that ran npm install and executed the dropper at install time; the deployed function carries no payload and never calls back to the C2 host.

Would a package-analysis tool like Socket have caught the malicious axios versions before install?

Socket inspects new package versions for install-time network access, added postinstall scripts, and new dependency additions, which is exactly the shape of this attack (a new plain-crypto-js dependency with a postinstall hook). A Socket-enforced CI gate would in principle have flagged axios@1.14.1, but only for teams that route installs through Socket’s proxy, which most registries and runners do not do by default.

Does the same exposure apply to pnpm, yarn, or Bun, or only npm?

Yes. pnpm, yarn, and Bun all honor the postinstall lifecycle and resolve caret ranges against the registry, so any of them running a fresh install during the window would have executed the same setup.js dropper. The lockfile-based protections differ in name (npm ci, yarn install —immutable, pnpm install —frozen-lockfile) but the install-time exposure is identical across the JavaScript package managers.

sources · 3 cited

  1. Axios package compromise and remediation stepsvercel.comvendoraccessed 2026-06-29
  2. Mitigating the Axios npm supply chain compromisemicrosoft.comanalysisaccessed 2026-06-29