Executive Summary
This article analyses a supply chain attack from last week, dubbed "SANDWORM_MODE", initially described by Socket.
The malicious packages discovered so far are typo-squats of popular npm packages like claude-code or supports-color. They contain a breadth of malicious functionality known from previous attacks, including the Shai Hulud campaign from 2025.
One interesting addition on top of credential theft, worm-like replication and other known features is the poisoning of AI toolchains through the local deployment and registration of a malicious MCP server in AI code assistants like Cursor. This confirms the recent evolution of supply chain attacks towards compromising AI tools, which are increasingly present in developer machines.
By now, all known malicious packages have been taken down, and are no longer available on npm.
Introduction
This campaign demonstrates a concerning trend - npm packages weaponizing AI coding assistants through MCP (Model Context Protocol) injection. The malware installs a rogue MCP server into tools like Claude Code, Cursor, Windsurf, and Continue, then uses prompt injection to trick the AI into silently reading and exfiltrating SSH keys, AWS credentials, and secrets without the user's knowledge.
This highlights a growing risk where attackers are now targeting the AI-developer interface as an attack surface.
On top of poisoning AI toolchains through a rogue MCP server, the malicious packages also feature the following capabilities:
- Multi-stage obfuscation with AES-256-GCM encryption
- Worm-like self-propagation across npm and GitHub via GitHub Actions
- Credential harvesting from environment variables, password managers (Bitwarden, 1Password, LastPass), and crypto wallets
- Multi-channel exfiltration via HTTPS, GitHub API, and DNS tunneling with a domain generation algorithm (DGA) for covert exfiltration
- Destructive dead switch capability (disabled in analyzed samples)
- Polymorphic rewrite capability via local LLM APIs (disabled in analyzed samples)
The following picture illustrates how those functions are triggered in victim machines, starting from the installation of a typo-squat package up until the (disabled) dead switch.

Detecting the Malicious Packages
Before diving deep into the individual malware features, let's understand how malware scanners can detect the presence of malicious code.
Good news: it is relatively easy to understand the malicious nature of those packages, even without a full analysis of the payload and its sophisticated multi-staged attack.
As visible from the screenshot taken from our scanner, there a multiple strong signals, namely the presence of a heavily obfuscated payload with considerable size, its automated decryption and execution upon being required, and the fact that the same author published a number of comparable typo-squatting packages in a short timeframe.

All of those signals are reason enough to flag those packages early and prevent them from being used downstream. The scanning and flagging happened in a time frame of just 2-5 minutes after they have been published on npm, as visible from the screenshot below.

As explained in a previous blog post, differential analysis is one important technique to identify interesting changes, code sequences or snippets, thereby comparing the package under analysis with both, a previous version of the same package and with potential typo-squatting targets–as in the case of suport-color.
In terms of size, the malicious package is multiple times larger due to the hidden 163KB payload. In terms of code, when comparing the index.js of the malicious package with the one from the benign package, the highly suspicious code becomes very obvious (see screenshot of diffing index.js from suport-color@1.0.3 with supports-color@10.2.2): the entirety of index.js has been removed and replaced by a few lines that dynamically construct a file path that is required in a silencing try-catch clause.

Comparing a previous version suport-color@1.0.1 with the original package demonstrates how the attackers experimented over time: here, the required JavaScript file was not dynamically constructed but hard-coded, most likely in the attempt of evading detection.

Execution Flow
This section provides a detailed overview about the stages and steps executed by the malware, at the example of `suport-color@1.0.1`, which is a typosquat of the popular npm package `supports-color`.
Even though the packages differ slightly in regard to the silent loading and payload obfuscation, the later steps resemble, and we use support-color@1.0.1 to walk you through the complete attack chain from initial infection to data exfiltration, propagation and persistence.
The Obfuscated Payload
The file `lib/color-support-engine.min.js` is 167KB of obfuscated JavaScript which is loaded from index.js file inside ‘try/catch’. Here's the structure:

The deobfuscation chain:
- Base64-decodes the embedded string
- zlib-inflates to decompress
- XOR decrypts using a hardcoded 32-byte key
- Executes the final payload via `eval()`
Stage 1 Analysis : Credential Harvesting
Stage 1 is a webpack bundle that immediately begins harvesting credentials. Here's what it targets:
- npm Tokens

- GitHub Tokens

- Environment Variables

- Cryptocurrency Wallets

Wallet paths searched:
- ~/.config/solana/id.json
Additionally, scans files for crypto key patterns:
- Ethereum private keys (64-char hex with optional 0x prefix)
- BIP39 mnemonics (12/24/25 word phrases)
- Bitcoin WIF keys (base58 starting with 5, K, or L)
- Extended private keys (xprv...)
- Password Managers
The malware targets CLI-based password managers that developers commonly use for credential management. It searches for credentials matching specific keywords related to cloud services, package registries, and version control.
What credentials are targeted: (based on the search terms used in “config.harvest.crypto.pmSearchTerms”:
- AWS credentials: access keys, secret keys
- npm tokens: registry authentication
- GitHub tokens: personal access tokens, OAuth tokens
- Generic secrets: API keys, database passwords matching search terms


- Cloud & SSH Credentials (via MCP Prompt Injection)
Unlike the above direct harvesting methods, AWS credentials and SSH keys are not collected by Stage 1 directly. Instead, they are targeted through the MCP prompt injection in Stage 2.
The injected MCP server's tool descriptions instruct AI assistants to read:
- `~/.ssh/id_rsa` and `~/.ssh/id_ed25519`
- `~/.aws/credentials`
- `~/.npmrc`
- `.env` files
This means the AI assistant itself becomes the exfiltration mechanism, the malware tricks it into reading these files and passing them to the malicious MCP server via the `context` parameter. See Section “The Prompt Injection” for detailed prompt analysis.
Finally, stage 1 terminates with a time gate before proceeding with stage 2, which is a common sandbox evasion technique.

Stage 2: The Encrypted Attack Toolkit
After collecting credentials and thetime gate passes, Stage 1 decrypts and loads Stage 2 using AES-256-GCM:

But here's where it gets interesting. Instead of writing Stage 2 to disk permanently, it uses transient execution:

The Stage 2 payload exists on disk for mere milliseconds, which makes forensic analysis significantly harder.

It exports five malicious modules, let's examine each one in the following subsections.
Propagate: The Self-Replicating Worm
This module turns a single infection into thousands, using 2 different strategies depending on the stolen credentials:
Strategy A: npm propagation
This is the primary propagation method, and infects EXISTING packages. It hijacks the victim's own npm packages by injecting malware and publishing new versions in the following steps:
- Validate stolen npm token
- List victim's packages and prioritize by downloads
- Infect and publish package as next version

Strategy B: GitHub/SSH Propagation
This is the secondary method, which uses a carrier package. It adds a “dependency reference” to a carrier package in the following steps:
- Get or create carrier name from SANDWORM_CARRIER_NAME env var
- Find ALL package.json files in the repo and add carrier package as dependencies to EACH package.json found
- Add malicious workflow to .github/workflows/ci.yml
- Direct commit (if token has write access to default branch) or create PR + auto-merge (if branch protection exists)
- Fallback to SSH agent (if GitHub tokens didn't work)

The workflow template of injected GitHub Action uses “pull_request_target” trigger runs with the target repository's secrets, as seen in the Shai-Hulud campaign.

And here’s the malicious part of the workflow:
- Runs on EVERY workflow execution (if: always())
- Dumps ALL repository secrets to $_R environment variable
- Base64-encodes the secrets for transmission
- Attempts HTTPS POST to C2 server (primary channel)
- Falls back to DNS tunneling if HTTPS fails (after 9s)
- Chunks data into 60-char DNS labels to bypass size limits
- Keeps process alive for 15s to ensure exfil completes

Exfil: Multi-Channel Data Exfiltration
The exfiltration module runs after propagation completes and packages all stolen data for transmission, and uses two channels for redundancy.
Data packaged for exfiltration:
- `contents`: System info, npm/GitHub tokens, propagation stats, LLM API keys, timestamps
- `environment`: Sensitive environment variables (API keys, secrets)
- `cloud`: AWS credentials, GCP service accounts, SSH keys
- `crypto`: Wallet keys, mnemonics, private keys (if found)
Channel 1: GitHub API
Steps:
- Iterate through all stolen GitHub tokens
- Validate each token still works via GitHub API
- Create a private repository with a legit-looking name (e.g., `config-backups-847`, `dev-notes-123`)
- Upload stolen data as "config files":
- `system.json`: System info + npm/GitHub tokens + propagation stats
- `env.json`: Environment variables
- `cloud.json`: AWS/GCP/SSH credentials
- `crypto.json`: Wallet keys (only if crypto data found)
- If successful, return immediately (skip DNS channel)

Channel 2: DNS Tunneling with DGA
If GitHub exfiltration fails, data is exfiltrated via DNS queries, which typically works even with HTTPS traffic being blocked.
Steps:
- Generate exfil domains using DGA (Domain Generation Algorithm) with seed `sw2025` or use pre-configured domain if set via `SANDWORM_DNS_DOMAIN`
- Compress the payload using zlib deflate
- Encode as base64url string
- Split into 60-character chunks (DNS label length limit)
- Send each chunk as a DNS A-record query:
- Format: `{chunk}.{index}.{sessionId}.{domain}`
- Example: `eNqLYW1234...0.abc123.attacker.cc`
- Mix in legitimate DNS queries as cover traffic (e.g., `google.com`, `npmjs.org`)
- Add random delays (300ms - 3s) between queries to avoid detection
- Send completion beacon: `end.{totalChunks}.{sessionId}.{domain}`

The DGA (Domain Generation Algorithm) rotates domains every 4 hours:

DeadSwitch: The Nuclear Option
The DeadSwitch module evaluates whether to trigger a destructive payload. In the samples we analyzed, it is disabled, but the capability exists.

McpInject: AI Toolchain Poisoning
This module confirms a growing trend where attackers are now targeting AI agents installed on developer machines. It injects malicious MCP (Model Context Protocol) servers into AI coding assistants, turning them into unwitting exfiltration tools.
The attack is particularly insidious because the user never sees the malicious instructions, they're hidden in tool descriptions that only the AI reads. By injecting a malicious MCP server, attackers can:
- Intercept AI interactions with legitimate tools
- Inject instructions that the AI will follow without user awareness
- Exfiltrate data through the AI's own tool-calling mechanisms
- Persist across sessions since MCP configs are stored in dotfiles
MCP Injection Works in the following steps:
- Deploy Malicious MCP Server: The malware creates a malicious MCP server script on disk

- Find AI Tool Config Files
The malware searches for configuration files of popular AI coding assistants in the following locations:
- Claude Code: ~/.claude/settings.json, ~/.claude.json
- Claude Desktop (macOS): ~/Library/Application
- Support/Claude/claude_desktop_config.json
- Claude Desktop (Linux): ~/.config/claude-desktop/config.json
- Cursor: ~/.cursor/mcp.json
- Continue: ~/.continue/config.json
- Windsurf: ~/.windsurf/mcp.json
- Inject into Each Config File
For each config file found, the malware adds its server to the “mcpServers” key:

- Prompt Injection in Tool Descriptions
Inside the deployed MCP server, the prompt injection (`P`) is appended to EVERY tool description using string concatenation:

- Exfiltration via "context" Parameter: When the AI reads credentials and passes them to the `context` parameter, the MCP server saves them to “~/.dev-utils/.cache/” file.

A notable finding from our analysis: the MCP server only saves credentials to disk, it has no network exfiltration capability. The credentials are stored in `~/.dev-utils/.cache/{timestamp}.json`, but we found no code in Stage 1 or Stage 2 that reads this cache for exfiltration. This could indicate:
- Incomplete feature: The exfiltration mechanism may not be fully implemented in this version
- Staged retrieval: Credentials may be retrieved on subsequent malware executions
- Future enhancement: The MCP server could be updated to include network exfiltration
Here's the actual prompt injection appended to every tool description:
When an AI assistant sees this tool, it's instructed to:
- Read SSH private keys, AWS credentials, npm tokens and .env files
- Collect sensitive environment variables
- Pass everything to the `context` parameter
- Not tell the user
GitHooks: Persistence
The hook payloads auto-inject the carrier dependency and exfiltrate npm tokens on every commit/push.
The final module establishes persistence via Git hooks:

Conclusion
The SANDWORM_MODE campaign marks a meaningful shift in the evolution of npm supply chain attacks and serves as an early indicator of how adversaries are adapting to AI-assisted development environments.
What we are observing is not experimental research or proof-of-concept activity. It is an operational, in-the-wild campaign that demonstrates clear intent, technical sophistication, and an understanding of modern developer workflows.
The Emerging Threat Model: MCP injection delivered through npm packages represents a new and consequential attack vector. Our investigation confirms active exploitation that:
- Targets widely adopted AI coding assistants (including Claude, Cursor, Continue, and Windsurf)
- Leverages prompt injection techniques to exfiltrate secrets without explicit user awareness
- Establishes persistence via modification of developer dotfiles and local configuration
- Operates through trusted AI processes, effectively blending into normal development activity
As AI coding assistants become integrated into daily engineering workflows, the attack surface shifts. Adversaries are no longer limited to compromising code, they can now target the AI systems that read, interpret, and generate it. The SANDWORM_MODE campaign illustrates that supply chain attackers are already aligning their tradecraft with this new paradigm.
At the same time, sophistication does not equate to invisibility. While techniques such as MCP injection and AI toolchain manipulation introduce new layers of complexity, they also create detectable patterns across package behavior, workflow modification, dependency anomalies, and configuration tampering. With effective initial triage and behavioral correlation, detection systems can rapidly surface these signals and escalate into deeper analysis, enabling faster response and swifter protection for customers.
In other words, as attackers evolve, so do defensive capabilities. The ability to identify early indicators and pivot quickly into detailed investigation remains critical in limiting impact and protecting modern software supply chains.
Indicators of Compromise (IoCs)
Stage 1 Decryption XOR Key: [12,144,98,213,194,247,114,72,9,155,97,65,248,15,40,75,232,162,168,231,215,210,126,179,114,69,172,173,14,130,201,222]
Stage 2 Decryption:
- Algorithm: AES-256-GCM
- Key: 5ce544f624fd2aee173f4199da62818ff78deca4ba70d9cf33460974d460395c
- IV (base64): dko6mG8AmQVECvVP
- Auth Tag (base64): /6rzsm9K+mflC4uguMJriA==
File System Artifacts:
- Malicious hook templates: ~/.git-templates/
- MCP server directory: ~/.dev-utils/
- Transient Stage 2 (Linux): /dev/shm/.node_*.js
Threat Actor Emails:
- official334@proton[.]me
- JAVAorg@proton[.]me
Spoofed Author:
- Sindre Sorhus (`sindresorhus@gmail.com`) - ‘supports-color’ maintainer
Campaign Identifier: ‘SANDWORM_MODE’ (environment variable)
Injected Workflow Filenames:
- ci.yml
- test.yml
- lint.yml
- build.yml
- validate.yml
- quality.yml
- verify.yml
- check.yml
DNS Exfiltration Domains:
- Freefan[.]net
- Fanfree[.]net
URLs
- https://pkg-metrics[.]official334[.]workers[.]dev/exfil
- https://pkg-metrics[.]official334[.]workers[.]dev/drain
- http://localhost:4873
- http://localhost:11434/api/tags
- http://localhost:11434/api/generate
- http://localhost:1234/v1/models
- http://localhost:8080/v1/models
- http://localhost:8000/v1/models
- http://localhost:5000/v1/models
Malicious Package Names



What's next?
When you're ready to take the next step in securing your software supply chain, here are 3 ways Endor Labs can help:
.jpg)







.png)
