Malicious versions drop remote access trojan

Article URL: https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan Comments URL: https://news.ycombinator.com/item?id=47582220 Points: 1349 # Comments: 509

Malicious versions drop remote access trojan
Malicious versions drop remote access trojan Photo: Hacker News

StepSecurity is hosting a community town hall on this incident on April 1st at 10:00 AM PT - Register Here .

axios is the most popular JavaScript HTTP client library with over 100 million weekly downloads.

On March 30, 2026, StepSecurity identified two malicious versions of the widely used axios HTTP client library published to npm: axios@1.14.1 and axios@0.30.4 .

The malicious versions inject a new dependency, plain-crypto-js@4.2.1 , which is never imported anywhere in the axios source code.

Its sole purpose is to execute a postinstall script that acts as a cross platform remote access trojan (RAT) dropper, targeting macOS, Windows, and Linux.

The dropper contacts a live command and control server and delivers platform specific second stage payloads.

After execution, the malware deletes itself and replaces its own package.json with a clean version to evade forensic detection.

There are zero lines of malicious code inside axios itself, and that's exactly what makes this attack so dangerous.

Both poisoned releases inject a fake dependency, plain-crypto-js@4.2.1 , a package never imported anywhere in the axios source, whose sole purpose is to run a postinstall script that deploys a cross-platform remote access trojan.

The dropper contacts a live command-and-control server, delivers separate second-stage payloads for macOS, Windows, and Linux, then erases itself and replaces its own package.json with a clean decoy.

A developer who inspects their node_modules folder after the fact will find no indication anything went wrong.

These compromises were detected by StepSecurity AI Package Analyst [ 1 ][ 2 ] and StepSecurity Harden-Runner.

We have responsibly disclosed the issue to the project maintainers.

StepSecurity Harden-Runner, whose community tier is free for public repos and is used by over 12,000 public repositories, detected the compromised axios package making anomalous outbound connections to the attacker's C2 domain across multiple open source projects.

For example, Harden-Runner flagged the C2 callback to sfrclak.com:8000 during a routine CI run in the backstage repository, one of the most widely used developer portal frameworks.

The Backstage team has confirmed that this workflow is intentionally sandboxed and the malicious package install does not impact the project.

The connection was automatically marked as anomalous because it had never appeared in any prior workflow run.

Harden-Runner insights for community tier projects are public by design, allowing anyone to verify the detection: https://app.stepsecurity.io/github/backstage/backstage/actions/runs/23775668703?tab=network-events
[Community Webinar] axios Compromised on npm: What We Know, What You Should Do
Join StepSecurity on April 1st at 10:00 AM PT for a live community briefing on the axios supply chain attack.

We'll walk through the full attack chain, indicators of compromise, remediation steps, and open it up for Q&A.

The attack was pre-staged across roughly 18 hours, with the malicious dependency seeded on npm before the axios releases to avoid “brand-new package” alarms from security scanners:
Step 1 - Maintainer Account Hijack
The attacker compromised the jasonsaayman npm account, the primary maintainer of the axios project.

The account’s registered email was changed to ifstap@proton.me — an attacker-controlled ProtonMail address.

Using this access, the attacker published malicious builds across both the 1.x and 0.x release branches simultaneously, maximizing the number of projects exposed.

Both axios@1.14.1 and axios@0.30.4 are recorded in the npm registry as published by jasonsaayman , making them indistinguishable from legitimate releases at a glance.

Both versions were published using the compromised npm credentials of a lead axios maintainer, bypassing the project's normal GitHub Actions CI/CD pipeline.

A critical forensic signal is visible in the npm registry metadata.

Every legitimate axios 1.x release is published via GitHub Actions with npm’s OIDC Trusted Publisher mechanism, meaning the publish is cryptographically tied to a verified GitHub Actions workflow.

axios@1.14.1 breaks that pattern entirely — published manually via a stolen npm access token with no OIDC binding and no gitHead :
There is no commit or tag in the axios GitHub repository that corresponds to 1.14.1 .

The release exists only on npm.

The OIDC token that legitimate releases use is ephemeral and scoped to the specific workflow — it cannot be stolen.

The attacker must have obtained a long-lived classic npm access token for the account.

Step 2 - Staging the Malicious Dependency
Before publishing the malicious axios versions, the attacker pre-staged plain-crypto-js@4.2.1 from account nrwise@proton.me .

This package:
The decoy version ( 4.2.0 ) was published 18 hours earlier to establish publishing history - a clean package in the registry that makes nrwise look like a legitimate maintainer.

plain-crypto-js: Complete Package Anatomy
A complete file-level comparison between plain-crypto-js@4.2.0 and plain-crypto-js@4.2.1 reveals exactly three differences.

Every other file (all 56 crypto source files, the README, the LICENSE, and the docs) is identical between the two versions:
The 56 crypto source files are not just similar; they are bit-for-bit identical to the corresponding files in the legitimate crypto-js@4.2.0 package published by Evan Vosberg.

The attacker made no modifications to the cryptographic library code whatsoever.

This was intentional: any diff-based analysis comparing plain-crypto-js against crypto-js would find nothing suspicious in the library files and would focus attention on package.json — where the postinstall hook looks, at a glance, like a standard build or setup task.

The anti-forensics stub ( package.md ) deserves particular attention.

After setup.js runs, it renames package.md to package.json .

The stub reports version 4.2.0 — not 4.2.1 :
This creates a secondary deception layer.

After infection, running npm list in the project directory will report plain-crypto-js@4.2.0 — because npm list reads the version field from the installed package.json , which now says 4.2.0 .

An incident responder checking installed packages would see a version number that does not match the malicious 4.2.1 version they were told to look for, potentially leading them to conclude the system was not compromised.

The difference between the real crypto-js@4.2.0 and the malicious plain-crypto-js@4.2.1 is a single field in package.json :
Step 3 - Injecting the Dependency into axios
The attacker published axios@1.14.1 and axios@0.30.4 with plain-crypto-js: "^4.2.1" added as a runtime dependency — a package that has never appeared in any legitimate axios release.

The diff is surgical: every other dependency is identical to the prior clean version.

Dependency comparison between clean and compromised versions:
When a developer runs npm install axios@1.14.1 , npm resolves the dependency tree and installs plain-crypto-js@4.2.1 automatically.

npm then executes plain-crypto-js ’s postinstall script, launching the dropper.

Phantom dependency: A grep across all 86 files in axios@1.14.1 confirms that plain-crypto-js is never imported or require() ’d anywhere in the axios source code.

It is added to package.json only to trigger the postinstall hook.

A dependency that appears in the manifest but has zero usage in the codebase is a high-confidence indicator of a compromised release.

The Surgical Precision of the Injection
A complete binary diff between axios@1.14.0 and axios@1.14.1 across all 86 files (excluding source maps) reveals that exactly one file changed : package.json .

Every other file — all 85 library source files, type definitions, README, CHANGELOG, and compiled dist bundles — is bit-for-bit identical between the two versions.

The complete package.json diff:
Two changes are visible: the version bump ( 1.14.0 → 1.14.1 ) and the addition of plain-crypto-js .

There is also a third, less obvious change: the "prepare": "husky" script was removed.

husky is the git hook manager used by the axios project to enforce pre-commit checks.

Its removal from the scripts section is consistent with a manual publish that bypassed the normal development workflow — the attacker edited package.json directly without going through the project's standard release tooling, which would have re-added the husky prepare script.

The same analysis applies to axios@0.30.3 → axios@0.30.4 :
Again — exactly one substantive change: the malicious dependency injection.

The version bump itself (from 0.30.3 to 0.30.4 ) is simply the required npm version increment to publish a new release; it carries no functional significance.

The RAT Dropper: setup.js - Static Analysis
setup.js is a single minified file employing a two-layer obfuscation scheme designed to evade static analysis tools and confuse human reviewers.

All sensitive strings — module names, OS identifiers, shell commands, the C2 URL, and file paths — are stored as encoded values in an array named stq[] .

Two functions decode them at runtime:
_trans_1(x, r) — XOR cipher.

The key "OrDeR_7077" is parsed through JavaScript’s Number() : alphabetic characters produce NaN , which in bitwise operations becomes 0 .

Only the digits 7 , 0 , 7 , 7 in positions 6–9 survive, giving an effective key of [0,0,0,0,0,0,7,0,7,7] .

Each character at position r is decoded as:
_trans_2(x, r) — Outer layer.

Reverses the encoded string, replaces _ with = , base64-decodes the result (interpreting the bytes as UTF-8 to recover Unicode code points), then passes the output through _trans_1 .

The dropper’s entry point is _entry("6202033") , where 6202033 is the C2 URL path segment.

The full C2 URL is: http://sfrclak.com:8000/6202033
StepSecurity fully decoded every entry in the stq[] array.

The recovered plaintext reveals the complete attack:
The complete attack path from npm install to C2 contact and cleanup, across all three target platforms.

Full Annotated Walkthrough of setup.js
With all strings decoded, the dropper's full logic can be reconstructed and annotated.

The following is a de-obfuscated, commented version of the _entry() function that constitutes the entire dropper payload.

Original variable names are preserved; comments are added for clarity.

The name com.apple.act.mond likely abbreviates "Activity Monitor Daemon" to blend with legitimate Apple processes using Apple's reverse-DNS daemon naming convention.

Persistent artifact: %PROGRAMDATA%\wt.exe (copy of PowerShell).

On Linux and all other platforms:
Complete Platform Payloads (Fully Resolved)
The following are the complete, fully resolved payloads as they execute on victim machines, with all template placeholders replaced with actual values using campaign ID 6202033 and C2 base URL http://sfrclak.com:8000/ .

When executed, this AppleScript expands to the following shell command:
The & at the end detaches nohup from the shell started by execSync , causing it to be re-parented to PID 1 ( init ) when that shell exits, as confirmed in the Harden-Runner process events where nohup (PID 2400) shows ppid: 1 .

The dropper's if/else if/else chain covers darwin , win32 , and everything else (Linux).

There is no explicit check for "linux" — anything that is not macOS or Windows falls through to the Linux command.

This means the dropper also targets FreeBSD, Android (Node.js on Termux), and any other Unix-like system where curl and python3 are available.

Container environments running Node.js are equally affected.

Each platform sends a distinct POST body to the same C2 endpoint:
The packages.npm.org/ prefix attempts to make traffic appear as benign npm registry communication in network logs and SIEMs.

Self-Cleanup - Hiding the Evidence
After launching the platform-specific payload, setup.js performs three forensic cleanup steps in sequence:
Post-infection inspection of node_modules/plain-crypto-js/package.json shows a completely clean manifest.

Running npm audit reveals nothing.

The only persistent evidence is the existence of the node_modules/plain-crypto-js/ directory itself — this package never appeared in any legitimate axios version.

Why the directory presence still matters: Even after cleanup, the existence of node_modules/plain-crypto-js/ is sufficient evidence of compromise — this package is not a dependency of any legitimate axios version.

If you find this directory, the dropper ran.

Runtime Execution Validation with StepSecurity Harden-Runner
Static analysis of the obfuscated dropper told us what the malware intended to do.

To confirm it actually executes as designed, we installed axios@1.14.1 inside a GitHub Actions runner instrumented with StepSecurity Harden-Runner in audit mode.

Harden-Runner captures every outbound network connection, every spawned process, and every file write at the kernel level — without interfering with execution in audit mode, giving us a complete ground-truth picture of what happens the moment npm install runs.

The full Harden-Runner insights for this run are publicly accessible: app.stepsecurity.io/github/actions-security-demo/compromised-packages/actions/runs/23776116077
Network Events: C2 Contact Confirmed Across Two Workflow Steps
The network event log contains two outbound connections to sfrclak.com:8000 — but what makes this particularly significant is when they occur:
Two things stand out immediately:
Why both connections show calledBy: "infra" : When Harden-Runner can trace a network call to a specific Actions step through the runner process tree, it labels it "runner" .

The "infra" label means the process making the connection could not be attributed to a specific step — because the dropper used nohup ...

& to detach from the process tree.

The process was deliberately orphaned to PID 1 ( init ), severing all parent-child relationships.

This is the malware actively evading process attribution.

Process Tree: The Full Kill Chain as Observed at Runtime
Harden-Runner captures every execve syscall.

The raw process events reconstruct the exact execution chain from npm install to C2 contact:
The process tree confirms the exact execution chain decoded statically from setup.js .

Four levels of process indirection separate the original npm install from the C2 callback: npm → sh → node → sh → curl/nohup .

The nohup process (PID 2400) reporting ppid: 1 is the technical confirmation of the daemonization technique — by the time npm install returned successfully, a detached process was already running /tmp/ld.py in the background.

File Events: The Evidence Swap Caught in Real Time
The file event log captures every file write by PID.

The plain-crypto-js/package.json entry shows two writes from two different processes — directly confirming the anti-forensics technique described in static analysis:
The 36-second gap between the two writes is the execution time of the dropper — it wrote the second file only after successfully launching the background payload.

Harden-Runner flagged this as a “Source Code Overwritten” file integrity event.

Post-infection, any tool that reads node_modules/plain-crypto-js/package.json will see the clean manifest.

The write event log is the only runtime artifact that proves the swap occurred.

Step 1 – Check for the malicious axios versions in your project:
Step 2 – Check for plain-crypto-js in node_modules :
If setup.js already ran, package.json inside this directory will have been replaced with a clean stub.

The presence of the directory alone is sufficient evidence the dropper executed.

Step 3 – Check for RAT artifacts on affected systems:
Step 4 – Check CI/CD pipelines:
Review pipeline logs for any npm install executions that may have pulled axios@1.14.1 or axios@0.30.4 .

Any pipeline that installed either version should be treated as compromised and all injected secrets rotated immediately.

For the Community: Recovery Steps
1.

Downgrade axios to a clean version and pin it.

2.

Add an overrides block to prevent transitive resolution back to the malicious versions
3.

Remove plain-crypto-js from node_modules.

4.

If a RAT artifact is found: treat the system as fully compromised.

Do not attempt to clean in place - rebuild from a known-good state.

Rotate all credentials on any system where the malicious package ran: npm tokens, AWS access keys, SSH private keys, cloud credentials (GCP, Azure), CI/CD secrets, and any values present in .env files accessible at install time.

5.

Audit CI/CD pipelines for runs that installed the affected versions.

Any workflow that executed npm install with these versions should have all injected secrets rotated.

6.

Use --ignore-scripts in CI/CD as a standing policy to prevent postinstall hooks from running during automated builds:
7.

Block C2 traffic at the network/DNS layer as a precaution on any potentially exposed system
For StepSecurity Enterprise Customers
Harden-Runner is a purpose-built security agent for CI/CD runners.

It enforces a network egress allowlist in GitHub Actions, restricting outbound network traffic to only allowed endpoints.

Both DNS and network-level enforcement prevent covert data exfiltration.

The C2 callback to sfrclak.com:8000 and the payload fetch in the postinstall script would have been blocked at the network level before the RAT could be delivered.

Harden-Runner also automatically logs outbound network traffic per job and repository, establishing normal behavior patterns and flagging anomalies.

This reveals whether malicious postinstall scripts executed exfiltration attempts or contacted suspicious domains, even when the malware self-deletes its own evidence afterward.

The C2 callback to sfrclak.com:8000 was flagged as anomalous because it had never appeared in any prior workflow run.

Detect Compromised Developer Machines
Supply chain attacks like this one do not stop at the CI/CD pipeline.

The malicious postinstall script in plain-crypto-js@4.2.1 drops a cross-platform RAT designed to run on the developer's own machine, harvesting credentials, SSH keys, cloud tokens, and other secrets from the local environment.

Every developer who ran npm install with the compromised axios version outside of CI is a potential point of compromise.

StepSecurity Dev Machine Guard gives security teams real-time visibility into npm packages installed across every enrolled developer device.

When a malicious package is identified, teams can immediately search by package name and version to discover all impacted machines, as shown below with axios@1.14.1 and axios@0.30.4 .

Newly published npm packages are temporarily blocked during a configurable cooldown window.

When a PR introduces or updates to a recently published version, the check automatically fails.

Since most malicious packages are identified within 24 hours, this creates a crucial safety buffer.

In this case, plain-crypto-js@4.2.1 was published hours before the axios releases, so any PR updating to axios@1.14.1 or axios@0.30.4 during the cooldown period would have been blocked automatically.

npm Package Compromised Updates Check
StepSecurity maintains a real-time database of known malicious and high-risk npm packages, updated continuously, often before official CVEs are filed.

If a PR attempts to introduce a compromised package, the check fails and the merge is blocked.

Both axios@1.14.1 and plain-crypto-js@4.2.1 were added to this database within minutes of detection.

Search across all PRs in all repositories across your organization to find where a specific package was introduced.

When a compromised package is discovered, instantly understand the blast radius: which repos, which PRs, and which teams are affected.

This works across pull requests, default branches, and dev machines.

AI Package Analyst continuously monitors the npm registry for suspicious releases in real time, scoring packages for supply chain risk before you install them.

In this case, both axios@1.14.1 and plain-crypto-js@4.2.1 were flagged within minutes of publication, giving teams time to investigate, confirm malicious intent, and act before the packages accumulated significant installs.

Alerts include the full behavioral analysis, decoded payload details, and direct links to the OSS Security Feed.

StepSecurity has published a threat intel alert in the Threat Center with all relevant links to check if your organization is affected.

The alert includes the full attack summary, technical analysis, IOCs, affected versions, and remediation steps, so teams have everything needed to triage and respond immediately.

Threat Center alerts are delivered directly into existing SIEM workflows for real-time visibility.

We want to thank the axios maintainers and the community members who quickly identified and triaged the compromise in GitHub issue #10604 .

Their rapid response, collaborative analysis, and clear communication helped the ecosystem understand the threat and take action within hours.

We also want to thank GitHub for swiftly suspending the compromised account and npm for quickly unpublishing the malicious axios versions and placing a security hold on plain-crypto-js .

The coordinated response across maintainers, GitHub, and npm significantly limited the window of exposure for developers worldwide.

Source: This article was originally published by Hacker News

Read Full Original Article →

Share this article

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment

Maximum 2000 characters