Inside the Axios supply chain compromise - one RAT to rule them all

Elastic Security Labs analyzes a supply chain compromise of the axios npm package delivering a unified cross-platform RAT

Inside the Axios supply chain compromise - one RAT to rule them all

Elastic Security Labs released initial triage and detection rules for the Axios supply-chain compromise. This is a detailed analysis of the RAT and payloads.

Introduction

Elastic Security Labs identified a supply chain compromise of the axios npm package, one of the most depended-upon packages in the JavaScript ecosystem with approximately 100 million weekly downloads. The attacker compromised a maintainer account and published backdoored versions that delivered a cross-platform Remote Access Trojan to macOS, Windows, and Linux systems through a malicious postinstall hook.

Key takeaways

  • A compromised npm maintainer account (jasonsaayman) was used to publish two malicious versions of the widely used Axios HTTP client — 1.14.1 (tagged latest) and 0.30.4 (tagged legacy) — meaning a default npm install axios resolved to a backdoored package
  • The malicious JavaScript deploys platform-specific stage-2 implants for macOS, Windows, and Linux
  • All three stage-2 payloads are implementations of the same RAT — identical C2 protocol, command set, beacon cadence, and spoofed user-agent, written in PowerShell (Windows), C++ (macOS), and Python (Linux)
  • The dropper performs anti-forensic cleanup by deleting itself and swapping its package.json with a clean copy, erasing evidence of the postinstall trigger from node_modules

Preamble

On March 30, 2026, Elastic Security Labs detected a supply chain compromise targeting the axios npm package through automated supply-chain monitoring. The attacker gained control of the npm account belonging to jasonsaayman, one of the project's primary maintainers, and published two backdoored versions within a 39-minute window.

The axios package is one of the most widely depended-upon HTTP client libraries in the JavaScript ecosystem. At the time of discovery, both the latest and legacy dist-tags pointed to compromised versions, ensuring that the majority of fresh installations pulled a backdoored release.

The malicious versions introduced a single new dependency: plain-crypto-js, a purpose-built package whose postinstall hook silently downloaded and executed platform-specific stage-2 RAT implants from sfrclak[.]com:8000.

What makes this campaign notable beyond its blast radius is the stage-2 tooling. The attacker deployed three parallel implementations of the same RAT — one each for Windows, macOS, and Linux — all sharing an identical C2 protocol, command structure, and beacon behavior. This isn't three different tools; it's a single cross-platform implant framework with platform-native implementations.

Elastic Security Labs filed a GitHub Security Advisory to the axios repository on March 31, 2026 at 01:50 AM UTC to coordinate disclosure and ensure the maintainers and npm registry could act on the compromised versions.

As the community flagged the compromise on social media, Elastic Security Labs shared early findings publicly to help defenders respond in real time.

This post covers the full attack chain: from the npm-level supply chain compromise through the obfuscated dropper, to the architecture of the cross-platform RAT and the meaningful differences between its three variants.

Campaign overview

The compromise is evident from the npm registry metadata. The maintainer email changed from jasonsaayman@gmail[.]com — present on all prior legitimate releases — to ifstap@proton[.]me on the malicious versions. The publishing method also changed:

VersionPublished ByMethodProvenance
axios@1.14.0 (legitimate)jasonsaayman@gmail[.]comGitHub Actions OIDCSLSA provenance attestations
axios@1.14.1 (compromised)ifstap@proton[.]meDirect CLI publishNone
axios@0.30.4 (compromised)ifstap@proton[.]meDirect CLI publishNone

The shift from a trusted OIDC publisher flow with SLSA provenance to a direct CLI publish with a changed email is a clear indicator of unauthorized access.

Timeline

  • 2026-02-18 17:19 UTCaxios@0.30.3 published legitimately by jasonsaayman@gmail[.]com
  • 2026-03-27 19:01 UTCaxios@1.14.0 published legitimately via GitHub Actions OIDC
  • 2026-03-30 05:57 UTCplain-crypto-js@4.2.0 published by nrwise (nrwise@proton.me) — clean decoy to build registry history
  • 2026-03-30 23:59 UTCplain-crypto-js@4.2.1 published by nrwise — malicious version with postinstall backdoor
  • 2026-03-31 00:21 UTCaxios@1.14.1 published by compromised account — tagged latest
  • 2026-03-31 01:00 UTCaxios@0.30.4 published by compromised account — tagged legacy

Affected packages

  • axios@1.14.1 — Malicious, tagged latest at time of discovery
  • axios@0.30.4 — Malicious, tagged legacy at time of discovery
  • plain-crypto-js@4.2.0 — Clean decoy, published to build registry history
  • plain-crypto-js@4.2.1 — Malicious, payload delivery vehicle (postinstall backdoor)

Safe versions: axios@1.14.0 (last legitimate 1.x release with SLSA provenance) and axios@0.30.3 (last legitimate 0.30.x release).

The attacker tagged both the latest and legacy channels, maximizing the blast radius across projects using either the current or legacy axios API.

Code analysis

Stage 1: The plain-crypto-js dropper

The entire delivery chain hinges on npm's postinstall lifecycle hook. Installing either compromised axios version pulls plain-crypto-js@^4.2.1 as a dependency, which declares:

"scripts": {
  "postinstall": "node setup.js"
}

This causes setup.js to execute automatically during npm install — no user interaction required.

The setup.js file uses a two-layer encoding scheme to conceal its behavior:

  • Layer 1: String reversal followed by Base64 decoding
  • Layer 2: XOR cipher using the key OrDeR_7077 with a position-dependent index (7 * i² % 10)

All critical strings, module names, URLs, shell commands are stored in an encoded array stq[] and decoded at runtime. The decoded contents reveal the operational infrastructure:

Platform-specific delivery

After decoding its string table, the dropper checks os.platform() and branches into one of three delivery routines. Each sends an HTTP POST to http://sfrclak[.]com:8000/6202033 with a platform-specific body — packages.npm.org/product0 (macOS), packages.npm.org/product1 (Windows), packages.npm.org/product2 (Linux) — allowing the C2 to serve the correct payload from a single endpoint. The packages.npm.org/ prefix is a deliberate attempt to make outbound traffic appear as benign npm registry communication in network logs:

PlatformDelivery MethodStage-2 LocationDisguise
macOSAppleScript via osascript downloads binary with curl/Library/Caches/com.apple.act.mondApple system daemon
WindowsVBScript downloads .ps1 via curl, executes via renamed PowerShell (%PROGRAMDATA%\wt.exe)%TEMP%\6202033.ps1 (transient)Windows Terminal
LinuxDirect curl download and python3 execution/tmp/ld.pyNone

Anti-forensics

The dropper performs two cleanup actions:

  1. Self-deletion: setup.js removes itself via fs.unlink(__filename)
  2. Package manifest swap: A clean file named package.md (containing a benign version 4.2.0 configuration with no postinstall hook) is renamed to package.json, overwriting the malicious version

Post-incident inspection of node_modules/plain-crypto-js/package.json reveals no trace of the postinstall trigger. The malicious setup.js is gone. Only the lockfile and npm audit logs retain evidence.

Stage 2: Cross-platform RAT

The three stage-2 payloads: PowerShell for Windows, compiled C++ for macOS, Python for Linux are not three different tools. They are three implementations of the same RAT specification, sharing an identical C2 protocol, command set, message format, and operational behavior. The consistency strongly indicates a single developer or tightly coordinated team working from a shared design document.

Shared architecture

The following properties are identical across all three variants:

  • C2 transport: HTTP POST
  • Body encoding: Base64-encoded JSON
  • User-Agent: mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)
  • Beacon interval: 60 seconds
  • Session UID: 16-character random alphanumeric string, generated per-execution
  • Outbound message types: FirstInfo, BaseInfo, CmdResult
  • Inbound command types: kill, peinject, runscript, rundir
  • Response command types: rsp_kill, rsp_peinject, rsp_runscript, rsp_rundir

The spoofed IE8/Windows XP user-agent string is particularly notable, it is anachronistic on all three platforms, and its presence on a macOS or Linux host is a strong detection indicator.

Initialization and reconnaissance

On startup, each variant:

  1. Generates a session UID — 16 random alphanumeric characters, included in every subsequent C2 message
  2. Detects OS and architecture — reports platform-specific identifiers (e.g., windows_x64, macOS, linux_x64)
  3. Enumerates initial directories of interest (user profile, documents, desktop, config directories)
  4. Sends a FirstInfo beacon containing the UID, OS identifier, and directory snapshot

After initialization, the implant enters the main loop. The first BaseInfo heartbeat includes a comprehensive system profile. The same categories of data are collected on all platforms, though the underlying APIs differ:

Data CollectedWindows SourcemacOS SourceLinux Source
Hostname%COMPUTERNAME% env vargethostname()/proc/sys/kernel/hostname
Username%USERNAME% env vargetuid() + getpwuid()os.getlogin()
OS versionWMI / registrysysctlbyname("kern.osproductversion")platform.system() + platform.release()
TimezoneSystem timezonelocaltime_r()datetime.timezone
Boot timeSystem uptimesysctl("kern.boottime")/proc/uptime
Install dateRegistry / WMIstat("/") or sysctlctime of /var/log/installer or /var/log/dpkg.log
Hardware modelWMIsysctlbyname("hw.model")/sys/class/dmi/id/product_name
CPU typeWMIsysctlbyname()platform.machine()
Process listFull PID, session, name, pathpopen("ps") (up to 1000)Full /proc enumeration (PID, PPID, user, cmdline)

Subsequent heartbeats are lightweight, containing only a timestamp to confirm the implant is alive.

Command dispatch

The C2 response is parsed as JSON, and the type field determines the action. All three variants implement the same four commands:

kill — Self-termination. Sends an rsp_kill acknowledgment and exits. The Windows variant's persistence mechanism (registry key + batch file) survives the kill command unless explicitly cleaned up; the macOS and Linux variants have no persistence of their own.

runscript — Script/command execution. The operator's primary interaction command. Accepts a Script field (code to execute) and a Param field (arguments). When Script is empty, Param is run directly as a command. The execution mechanism is platform-native:

PlatformExecution Mechanism
WindowsPowerShell with -NoProfile -ep Bypass
macOSAppleScript via /usr/bin/osascript
LinuxShell via subprocess.run(shell=True) or Python via python3 -c

peinject — Binary payload delivery. Despite the Windows-centric naming ("PE inject"), all three platforms implement this as a way to drop and execute binary payloads:

PlatformImplementation
WindowsReflective .NET assembly loading via [System.Reflection.Assembly]::Load()
macOSBase64-decodes and drops a binary, executes with operator-supplied parameters.
LinuxBase64-decodes a binary to /tmp/.<random 6-char string> (hidden file), launches via subprocess.Popen().

The Windows implementation has in-memory execution with no file drop but without disabling AMSI which will certainly flag on the Assembly load. The macOS and Linux variants take the simpler approach of writing a binary to disk and executing it directly.

rundir — Directory enumeration. Accepts paths and returns detailed file listings (name, size, type, creation/modification timestamps, child count for directories). Allows the operator to interactively browse the filesystem.

Capability summary

CapabilityWindows (PowerShell)macOS (C++)Linux (Python)
PersistenceRegistry Run key + hidden .batNoneNone
Script executionPowerShellAppleScript via osascriptShell or Python inline
Binary injectionReflective .NET load injecting into cmd.exeBinary drop + executeBinary drop to /tmp/ + execute
Anti-forensicsHidden windows, temp file cleanupHidden temp .scptHidden /tmp/.XXXXXX files

Attribution

The macOS Mach-O binary delivered by the plain-crypto-js postinstall hook exhibits significant overlap with WAVESHAPER, a C++ backdoor tracked by Mandiant and attributed to UNC1069, a DPRK-linked threat cluster.

Conclusion

This campaign demonstrates the continued attractiveness of the npm ecosystem as a supply chain attack vector. By compromising a single maintainer account on one of the JavaScript ecosystem's most depended-upon packages, the attacker gained a delivery mechanism with potential reach into millions of environments.

The toolkit's most reliable detection indicator is also its most curious design choice: the IE8/Windows XP user-agent string hardcoded identically across all three platform variants. While it provides a consistent protocol fingerprint for C2 server-side routing, it is trivially detectable on any modern network — and is an immediate anomaly on macOS and Linux hosts.

Elastic Security Labs will continue monitoring this activity cluster and will update this post with any additional findings.

MITRE ATT&CK

Elastic uses the MITRE ATT&CK framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks.

Tactics

Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action.

Techniques

Techniques represent how an adversary achieves a tactical goal by performing an action.

Observations

The following observables were discussed in this research.

ObservableTypeNameReference
617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101SHA-2566202033.ps1Windows payload
92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645aSHA-256com.apple.act.mondMacOS payload
fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cfSHA-256ld.pyLinux payload
sfrclak[.]comDOMAINC2
142.11.206[.]73ipv4-addrC2

References

The following were referenced throughout the above research:

Share this article