Sep 16, 2025

NPM Software Supply Chain Security Incident

Preface

In the past two days, the NPM registry has once again suffered a serious supply chain attack. This incident appears very similar in nature to the previous LedgerHQ library compromise — its main goal was also to steal cryptocurrency. Although this attack ultimately failed to steal any funds, the impact was huge. So far, at least 24 core libraries have been confirmed to be compromised, including well-known JavaScript libraries like chalk and debug. These libraries collectively receive over 2 billion downloads per week, which means this event has had a massive impact on the JavaScript ecosystem.

Incident Background

According to @StarPlatinumSOL, this issue was first discovered by a JavaScript developer who encountered a strange build error. The error message stated that the native fetch function was undefined. Upon inspecting the code, they discovered a heavily obfuscated line of JavaScript that contained a suspicious function with a name similar to checkethereumw.

After further investigation, it was confirmed that well-known NPM libraries such as chalk and debug had been compromised. All of these libraries were maintained under the same NPM account, qix. Initially, 18 compromised libraries were identified, but subsequent checks revealed that additional libraries — including duckdb and coveops — also contained malicious code. The currently known list of malicious libraries and their affected versions is as follows:

Technical Analysis

Taking the chalk library as an example, the affected version is 5.6.1. The infected file path is "chalk/package/source/index.js". As shown, a single line of obfuscated JavaScript — the malicious payload — was inserted at line 11.

By using the open-source tool “decode-js,” the obfuscated code can be deobfuscated, making it relatively readable and easier to analyze.

It turns out the code contains a large number of hardcoded cryptocurrency wallet addresses, which are presumably the attacker’s receiving addresses for siphoned funds.

With Kimi’s help, I further transformed the code for readability and focused on several core points. First is the environment check: it determines whether an Ethereum context exists in the current browser environment. If Ethereum accounts are successfully obtained, it runs the runMask function to intercept and tamper with Ethereum transactions. Otherwise, it runs only the initLocal function to hijack transaction addresses, replacing them with entries from the attacker’s recipient address list mentioned above.

Both core functions are essentially hook-based hijacks, but with different targets: runMask is designed to intercept transaction request packets, while initLocal intercepts response data. In the Ethereum environment, the attack hooks three interfaces on window.ethereum'request', 'send', and 'sendAsync'. By force-overwriting them via Object.defineProperty, it can (as Kimi notes) bypass the read-only protections used by many front-end frameworks.

After hooking those three interfaces, the attacker uses a createProxy function to intercept and modify Ethereum transactions. This approach silently alters the recipient address at the moment the request is made, making it hard for users to notice.

The tampered transaction parameters replace the transfer recipient with:0xFc4a4858bafef54D1b1d7697bfb5c52F4c166976

This means that whenever a user initiates a normal transfer or a contract call (e.g., approve or permit operations) on Ethereum, the destination address is automatically replaced with the attacker’s address.

For Solana transactions, all public keys (pubkey) are uniformly replaced with the attacker’s fixed public key:19111111111111111111111111111111

Looking at the hijacking in non-Ethereum environments: the code first hooks two request interfaces — fetch and XMLHttpRequest.prototype.send — then intercepts and modifies the response payloads to replace any embedded transfer addresses.

Additionally, the reassignment of fetch here is likely where the earlier build error was triggered. The attacker appears to have used the native fetch API without checking for its existence, causing an “undefined” error in environments where fetch isn’t supported.

The replacement process works by first detecting various cryptocurrency address formats using a built-in list of regular expressions, then selecting from the attacker’s address pool the one most similar to the original and substituting it in.

This selection process uses the Levenshtein distance algorithm (a method for calculating string edit distance) to find an address that is very close to the original one. The goal is to make only subtle changes so that users won’t easily notice — since with long strings like Ethereum addresses, most users typically only verify the first few and last few characters by eye.

0x03 Attack AttributionBecause this incident happened suddenly and had such a wide impact, it was quickly discovered and contained before it could spread further. The maintainer of the compromised NPM account, qix, promptly shared the phishing email he had received. The sender’s address was:support@npmjs.helpand the email was sent at September 7, 8:50 PM (U.S. Eastern Time).

The attacker posed as NPM support, claiming the email was about updating 2FA, tricking qix into clicking the link and ultimately stealing his account credentials.

Since the phishing link is no longer accessible(https[:]//www.npmjs[.]help/settings/qix/tfa/manageTfa?action=setup=totp),I checked the Wayback Machine (web.archive.org) for a snapshot. The archived version shows that the phishing site’s homepage was a near-perfect clone of the official NPM registry site. Based on the URI path of the phishing link, it is likely that the attackers used a proxy-based mirroring technique to replicate the real NPM site 1:1, tricking qix into logging in and thereby stealing his session credentials.

An analysis of the attacker’s email domain “npmjs.help” revealed that it was registered on September 6, just two days before the attack. This timing indicates that the attack was highly targeted and carefully planned.

Later, at 11:15 AM U.S. Eastern Time, qix posted on Bluesky stating that he had realized his account had been compromised and had begun the cleanup process.

Summary

This incident adds yet another textbook case to the growing list of software supply chain attacks. The entire event — from initial compromise, to propagation and impact, to discovery, cleanup, and analysis — unfolded in less than half a week. It serves as a powerful reminder that, while security defenses continue to mature, we can never afford to lower our guard.


Case Analysis of Encryption and Decryption Data Packets: Practical Implementation of AES Encryption and Signature Verification (Part 1)

Shortcut to Trouble: Analyzing a Trojan That Deploys Cobalt Strike