Web Security

The npm Chalk and Debug Attack Proves Again: The Web’s Trust Model is Broken

September 10th, 2025 | By Pedro Fortuna | 17 min read

On September 8th, 2025, Aikido reported a major supply chain attack affecting dozens of npm packages, including the hugely popular chalk and debug. In total, the compromised packages represent 2.6 billion weekly downloads across the ecosystem.


The malicious updates, injected after the attacker compromised the npm account of a well-known maintainer, targeted MetaMask wallets, attempting to intercept and hijack crypto transactions.


As shocking as the scale of this incident is, it highlights once again a problem we already know too well: the web’s trust model is broken.


The web rose to dominance because it is simple, portable, and composable. In software, composability means building larger systems by combining smaller, reusable components — very often written and maintained by someone else, such as maintainers, open-source contributors, or third-party vendors.


On the web, this idea is pushed to the extreme: modern apps may depend on hundreds of external libraries, each one a building block you didn’t have to write yourself. This is why we can develop applications in record time — but it also comes with a price. Every new dependency you add is a potential new attack vector.


In this post, we’ll unpack what this incident tells us about the web’s trust model, why the Client-Side remains the ultimate target, and how organizations can move from crossing their fingers to actually defending against these inevitable compromises.


The Web Trust Model is Broken


The strength of the web has always been its composability. Instead of reinventing the wheel, developers can stitch together countless libraries and frameworks to deliver applications faster than ever. But composability comes at a price: in most modern applications, the majority of the code is not written by you. It comes from strangers — maintainers you’ve never met, teams you don’t control, and code paths you probably never review in detail.


In practice, many developers place blind trust in third-party packages. They focus on securing the code they authored while assuming dependencies “just work.” From an application security perspective, this mindset is dangerous. A vulnerability or backdoor can hide anywhere in the codebase, and in today’s web, it’s far more likely to be in a dependency than in your own logic.


This isn’t theory; history has shown us what happens when that trust is misplaced:

  • In 2018, the event-stream compromise introduced malicious code through a maintainer handover, planting a backdoor deep inside countless projects.

  • In 2021, attackers hijacked the UAParser.js npm account, publishing versions that installed password stealers and cryptominers on developer machines.

  • In 2022, the maintainer of colors and faker deliberately sabotaged his own packages, breaking thousands of projects overnight and exposing the fragility of relying on community goodwill.

  • And most recently, the XZ Utils compromise (2024) showed us something even more unsettling: that attackers can patiently embed themselves as trusted maintainers for years, only to eventually deliver a backdoored release. It’s an extreme case, but it proves that even the people we trust most can become part of the attack surface.

The chalk and debug incident is just the latest reminder that blind trust is no security strategy. Supply-chain compromises are no longer rare exceptions — they are part of the threat landscape. If your defenses assume that upstream code will always remain trustworthy, you’re already playing a losing game.


Highlights From the Attack


As a company specializing in JavaScript security, we were eager to analyze the malicious payload in detail.


From deobfuscating the code, we found Aikido findings to be accurate: the payload was designed to run in the browser, intercept network and wallet calls, actively tampered with MetaMask wallet transactions, and replace legitimate recipient addresses with attacker-controlled ones.


We respectfully disagree with JD Staerk’s characterization of the code as ‘heavily obfuscated’.  Our analysis found the obfuscation was superficial. Yes — the code looks messy at first glance, but it is not resilient to reverse engineering and analysis. With modest automation and manual inspection, the core objects and behaviors can be exposed quickly. It appears the attacker aimed to slow down casual reviewers, not to withstand determined reverse engineering.


Example (Figure 1) — you can see how an obfuscated control object is trivially mapped back to readable identifiers:
Figure 1 — Obfuscated (left) vs. recovered representation (right)


For a full walkthrough of the malicious code’s execution flow, we recommend JD Staerk’s post, which provides a detailed step-by-step analysis. A few implementation details are especially noteworthy because they show the attacker’s intent and the reasons the attack was discovered quickly.


1) XHR monkey-patching (response rewriting)

The payload replaces XMLHttpRequest.prototype.send so it can inspect responses, parse JSON responses when appropriate, run a replaceAddress step, and then overwrite xhr.responseText/xhr.response with a tampered version.


This technique is broad and noisy: it will attempt to replace any string that looks like a wallet address in any response. Because it operates on a wide surface area (any XHR response), it increases the chance of detection and false positives, which likely contributed to the rapid discovery of the compromise.


Figure 2 - Monkey-patched XHR.prototype.send that replaces wallet address


2) Transaction Interception (wallet API hooking)

For active wallet manipulations, the payload wraps wallet RPC methods. The intercept wrapper inspects arguments, identifies methods like eth_sendTransaction or Solana signing methods, rewrites transaction objects via a txRewriter, and then forwards the call to the original method:


Figure 3 — Transaction interception and in-memory tampering


Because the payload hooks into the wallet signing flow, the attacker can change the transaction recipient just before it reaches the wallet for user confirmation — the precise moment where human attention is required, and therefore the ideal point to hijack funds if users don’t inspect the destination carefully.


3) Levenshtein-based address selection

Rather than blindly choosing any attacker address, the code uses a Levenshtein distance function to pick the attacker address that is visually most similar to the original recipient. This reduces the chance a human will notice the swap — whether skimming a UI or reviewing on a hardware wallet — a clever trick to fool the eye.

Figure 4 - Using levenshtein distance algorithm to pick the most visually similar  attacker address

4) The curious fetch error - exposing the attack

Ironically, one of the first public signals was a build error:


ReferenceError: fetch is not defined


The attacker used fetch calls assuming the runtime provided it. On older Node.js environments (where fetch is not global), this caused CI/CD builds to fail — a lucky fail for defenders. In modern Node versions where fetch exists, that same call would have silently executed, making detection harder. This reveals a tradeoff: assumptions that make code run broadly can also make it stealthier.


It makes us wonder: the threat actor used several clever tricks, but didn’t think to include a fetch polyfill.


Was the Attack Successful?

From a technical perspective, the attack seems to have been successful. We observed on-chain transfers to attacker-controlled addresses within the first 24 hours after the compromise. The following is a snapshot of those addresses and the balances transferred (net worths are shown as of publication):


From a technical perspective, the attack was successful. We were able to confirm that the following wallets and amounts were moved to some of the attacker’s wallets, all within one day.

Wallet

Portfolio Net Worth *

0xFc4a4858bafef54D1b1d7697bfb5c52F4c166976

$923.15

0xa4134741a64F882c751110D3E207C51d38f6c756

$1.32

0x93Ff376B931B92aF91241aAf257d708B62D62F4C

$0.44

Total

$924.91

* (Net worths captured at time of publishing; on-chain activity may continue — link these addresses to Blockscan for live updates.)



Viewed economically, the haul is surprisingly small — under $1,000 in on-chain receipts. That amount is very modest compared with what a functioning, widely-deployed clipper could have achieved, and far below what access to compromised, high-usage packages would fetch on illicit markets. In other words: this was not a dry-run — the attacker clearly intended to profit — but they misjudged either their scale, their opsec, or the payload’s stealth. A more sophisticated operator could have sold or leased control of these packages to third parties for a significantly higher return, or profited far more by running quieter, long-running exfiltration campaigns.


Why the Attack Wasn’t Worse

The attacker reused the same injected payload across all compromised packages. This made detection easier, as security vendors could quickly roll out signatures.


A more sophisticated attacker could have randomized the payloads, introduced polymorphism, or used lightweight obfuscation techniques to avoid detection by obfuscation and pattern-based detection.


In short: this could have been much worse.


The Client-Side Perspective


While the compromise originated upstream in the npm registry, the final delivery point of the malicious code was the Client-Side. The injected payload’s purpose was to tamper with crypto transactions running inside end-user browsers.


A web application developer cannot prevent a maintainer from being phished. The npm ecosystem cannot guarantee that no package will ever be compromised. But as an application owner, you can control what actually executes in your users’ browsers. And that is where the real battle is fought.


This is where Client-Side Monitoring becomes essential. With Jscrambler’s Webpage Integrity (WPI), every script that loads in the browser is continuously monitored for tampering and malicious behavior. It doesn’t matter if the compromised dependency came from a trusted maintainer or slipped through your CI pipeline: the moment it begins to act maliciously, WPI detects and stops it.


Applied to the chalk and debug attack, WPI would have:

  • Flagged and blocked the malicious code the moment it attempted to monkey-patch browser APIs like fetch or XMLHttpRequest to intercept network traffic.

  • Detected unauthorized attempts to hook into wallet objects such as window.ethereum and modify outgoing transactions before they were signed.

  • Prevented the exfiltration of sensitive data by blocking requests to attacker-controlled domains.

  • Detected changes to the JavaScript itself, updating the application inventory, alerting application owners and describing the new behavioral patterns introduced by the injected code.

In practice, this means that even if a compromised package made it into production, the attack would have failed at the very last mile. Users’ wallets would remain untouched because the malicious script would never be allowed to execute its intended behavior.


And that’s the key point: you can’t stop every supply chain compromise. Even packages with impeccable reputations and long histories of community trust, like those affected in this incident, can be compromised. But you can stop these compromises from ever impacting your users by securing the runtime environment where attackers ultimately need to succeed — the browser. Plus, it’s something that you have 


Parallel with PCI DSS


This attack illustrates exactly why PCI DSS v4 introduced two new eSkimming requirements:

  • 6.4.3 — maintaining an inventory of scripts and ensuring their integrity.

  • 11.6.1 — monitoring scripts for tampering or unauthorized changes.

Just like in e-commerce, the initial compromise is often unstoppable. What matters is what happens afterwards: the controls you put in place to stop malicious scripts from harming your users.
  

This isn’t just a credit card problem — it’s a web problem.


Conclusion

The npm chalk and debug compromise was a wake-up call at massive scale — 2.6 billion weekly downloads affected. The injected payload was a crypto-stealer, targeting MetaMask wallets and tampering with browser-level APIs. However, the real lesson is not limited to this one incident. It’s about the fragility of the web’s trust model:

  • Composability makes the web powerful, but also fragile.

  • Reputation is not security. Even trusted maintainers can be compromised.

  • Static audits alone will never be enough. Runtime monitoring is the only way to know what’s really happening inside your users’ browsers.

Supply chain attacks aren’t going away. By deploying Client-Side security controls, you can protect both your applications and your users — even when trust elsewhere fails.


The web’s trust model is broken. Runtime monitoring is how you stop it from breaking you.





Indicators of Compromise (IOCs):

Compromised npm packages

Package Name

Compromised Package Version

backslash

0.2.1

chalk-template

1.1.1

supports-hyperlinks

4.1.1

has-ansi

6.0.1

simple-swizzle

0.2.3

color-string

2.1.1

error-ex

1.3.3

color-name

2.0.1

is-arrayish

0.3.3

slice-ansi

7.1.1

color-convert

3.1.1

wrap-ansi

9.0.1

ansi-regex

6.2.1

supports-color

10.2.1

strip-ansi

7.1.1

chalk

5.6.1

debug

4.4.2

ansi-styles

6.2.2

proto-tinker-wc

0.1.87

@coveops/abi

2.0.1

duckdb

1.3.3

@duckdb/node-bindings

1.3.3

@duckdb/duckdb-wasm

1.29.2

@duckdb/node-api

1.3.3



Exfiltration Domain


npmjs[.]help

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Web Security

Stealing Seconds: Web Skimmer Compromises Casio UK and Growing Number of Websites

Jscrambler just uncovered a new batch of web skimmer infections affecting multiple websites, including casio.co.uk. So far, 17 victim websites are confirmed, though this number will likely increase...

January 31, 2025 | By Pedro Fortuna | 10 min read

Web Security

Stripe API Skimming Campaign: Additional Victims and Insights

A recently discovered web skimming campaign has introduced a novel technique that leverages a legacy Stripe API to validate stolen payment details before exfiltrating them.

April 2, 2025 | By Pedro Fortuna | 13 min read

Section Divider