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 are 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 walks through the complete attack chain of the SANDWORM_MODE campaign, from initial infection to data exfiltration, propagation, and persistence. We use suport-color@1.0.1 — a typosquat of the popular npm package supports-color — as our reference sample throughout.
While individual packages in the campaign differ slightly in how they silently load and obfuscate the initial payload, the core attack chain is consistent across all samples. As illustrated in the infection chain diagram earlier, the attack progresses from the installation of a typo-squat package through credential harvesting, worm propagation, and AI toolchain poisoning, up to the (disabled) destructive dead switch. The following subsections dissect each stage step by step.
The Obfuscated Payload
When a developer installs suport-color@1.0.1, the package's index.js silently loads a file called lib/color-support-engine.min.js — a 167KB blob of obfuscated JavaScript wrapped in a try/catch block so that any loading failure is silently swallowed.
The obfuscated payload follows a three-layer deobfuscation chain before executing the actual malware. The code hereafter shows the deobfuscated top-level routine.

Note how each layer is nested in a multi-steps deobfuscation process: the outermost function Base64-decodes the embedded string, passes the result through zlib decompression, and finally XOR-decrypts it with a hardcoded 32-byte key before calling eval() on the cleartext.
Stage 1 Analysis : Credential Harvesting
The decrypted Stage 1 payload is a webpack bundle that immediately begins harvesting credentials from the infected machine. It targets six categories of secrets, each using a different collection technique. The first five are collected directly by reading files and environment variables; the sixth — cloud and SSH credentials — is deferred to Stage 2's MCP prompt injection, which uses the developer's AI assistant as an unwitting exfiltration vector.
1. npm Tokens
The malware searches for .npmrc files in several standard locations — the user's home directory (~/.npmrc), the current project root, and any custom path set via the NPM_CONFIG_USERCONFIG environment variable. It extracts authentication tokens by matching the npm_ prefix and registry-scoped _authToken entries, normalizing different token formats before adding them to the exfiltration payload. A stolen npm token gives the attacker publish access to all packages the victim maintains, which is the foundation for the worm-like propagation in Stage 2.

2. GitHub Tokens
GitHub tokens are harvested from environment variables commonly set by CI/CD systems and developer workflows. The routine scans for variables containing tokens with well-known prefixes — ghp_ (personal access tokens), gho_ (OAuth tokens), and github_pat_ (fine-grained PATs). It also checks the GITHUB_TOKEN variable set automatically in GitHub Actions runners. These tokens are later used for both data exfiltration (creating private repos to stash stolen data) and lateral movement (committing malicious code to the victim's repositories).

3. Environment Variables
Beyond targeted token harvesting, the malware performs a broad sweep of all environment variables, filtering for those whose names or values suggest they contain secrets. It matches against keywords such as TOKEN, KEY, SECRET, PASSWORD, etc. This catch-all approach is designed to capture secrets from services the attacker didn't specifically anticipate — database connection strings, third-party API keys, internal service tokens, and similar.

4. Cryptocurrency Wallets
The malware targets cryptocurrency assets through two complementary techniques. First, it reads wallet files from known filesystem paths — starting with ~/.config/solana/id.json for Solana wallets. Second, it scans files in the user's home directory for patterns matching private key formats across multiple blockchains. This dual approach maximizes coverage: even if the developer doesn't use a standard wallet application, any private key material stored in dotfiles or configuration will be found.

5. Password Managers
The malware targets CLI-based password managers that developers commonly use for credential management — specifically Bitwarden, 1Password, and LastPass CLI. Rather than attempting to crack or dump the entire vault, it issues targeted search queries through the CLI tools using keywords defined in the malware's configuration (config.harvest.crypto.pmSearchTerms). This is a pragmatic approach: if the password manager CLI is authenticated (as it often is during active development sessions), the malware can silently retrieve individual credentials without triggering vault-wide access alerts.

What credentials are targeted (based on the search terms):
- 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

6. Cloud & SSH Credentials (via MCP Prompt Injection)
Unlike the five direct harvesting methods above, AWS credentials and SSH keys are not only collected by Stage 1 directly. Instead, they are targeted through the MCP prompt injection deployed in Stage 2 (see the McpInject section below).
The injected MCP server's tool descriptions instruct AI assistants to read:
~/.ssh/id_rsaand~/.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 the McpInject section and The Prompt Injection subsection below for the detailed prompt analysis.
Time Gate: Sandbox Evasion
After completing the credential harvest, Stage 1 does not immediately proceed to Stage 2. Instead, it enters a time gate — a deliberate delay designed to evade automated sandbox analysis. Most malware sandboxes execute samples for a limited time window (typically 30–120 seconds). By delaying the more aggressive Stage 2 operations, the malware ensures that sandbox reports will only capture the relatively benign-looking credential harvesting, missing the worm propagation, AI toolchain poisoning, and destructive capabilities that follow.

Stage 2: The Encrypted Attack Toolkit
The two-stage architecture serves a clear operational purpose. Stage 1 is lightweight and fast — it grabs credentials and establishes a time gate to survive sandbox analysis. Stage 2 contains the heavier, riskier operations — worm propagation, destructive capabilities, and AI toolchain poisoning — that would be more likely to trigger behavioral detection if bundled into the initial payload.
After the time gate elapses, Stage 1 decrypts and loads Stage 2 via AES-256-GCM: the code calls crypto.createDecipheriv('aes-256-gcm', key, iv), sets the auth tag, deciphers the embedded encrypted blob, and passes the resulting cleartext JavaScript to the module loader.

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, making forensic recovery significantly harder. On Linux, it uses /dev/shm/, a temporary file system mount.
Stage 2 exports five malicious modules that execute in sequence, each building on the credentials and access established by Stage 1:
- Propagate — spreads the malware to other npm packages and GitHub repositories using stolen credentials
- Exfil — packages all stolen data and transmits it through redundant channels (GitHub API, DNS tunneling)
- DeadSwitch — evaluates whether to trigger a destructive payload (disabled in analyzed samples)
- McpInject — installs a rogue MCP server into AI coding assistants for persistent, long-term credential access
- GitHooks — establishes local persistence through Git hook templates that re-infect on every commit

Let's examine each one.
1. Propagate: The Self-Replicating Worm
This module turns a single infection into potentially thousands. The propagation uses two different strategies depending on the stolen credentials available:
Strategy A: npm Propagation (Infects Existing Packages)
As the primary propagation method, the malware hijacks the victim's own npm packages — the ones they legitimately maintain — by injecting the malware payload and publishing new patch versions. This is particularly dangerous because downstream consumers who use semver ranges (e.g., ^1.2.0) will automatically pull the infected version on their next install.
The steps are:
- Validate stolen npm token against the registry API
- List all packages the victim maintains and prioritize by weekly download count (maximizing blast radius)
- Infect and publish package as next version (for each package)

Strategy B: GitHub/SSH Propagation (Uses Carrier Package)
GitHub tokens and/or SSH keys are the targets of this second propagation strategy. Instead of modifying existing packages directly, the malware injects a dependency reference to a "carrier" package — a malicious package the attacker controls.
The steps are:
- Get or create carrier name from the
SANDWORM_CARRIER_NAMEenvironment variable - Find all
package.jsonfiles in the repository and add the carrier package as a dependency to each one - Add a malicious GitHub Actions workflow to .
github/workflows/ci.yml - Direct commit (if token has write access to the default branch) or create a PR with auto-merge (if branch protection exists)
- Fallback to SSH agent (if GitHub tokens didn't work)

The injected GitHub Actions workflow uses the pull_request_target trigger — the same technique seen in the Shai-Hulud attack campaign from 2025. This trigger is dangerous because it runs with the target repository's secrets, not the PR author's, meaning the workflow has access to all repository secrets even when triggered by an external contributor.

And here's the malicious part of the workflow:
- Runs on every workflow execution (
if: always()) - Dumps all repository secrets to the
$_Renvironment variable - Base64-encodes the secrets for transmission
- Attempts HTTPS POST to the 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 the process alive for 15s to ensure exfiltration completes

2. Exfil: Multi-Channel Data Exfiltration
The exfiltration module runs after propagation completes and bundles the stolen data as follows for transmission:
contents: System info, npm/GitHub tokens, propagation stats, LLM API keys, timestampsenvironment: Sensitive environment variables (API keys, secrets)cloud: AWS credentials, GCP service accounts, SSH keyscrypto: Wallet keys, mnemonics, private keys (if found)
Two channels are used to ensure redundancy—the second one is skipped if the first is successful.
Channel 1: GitHub API
The primary exfiltration channel abuses the victim's own GitHub tokens to create private repositories that serve as dead drops for stolen data. The repository names are generated to look innocuous — names like config-backups-847 or dev-notes-123 — so they don't immediately raise suspicion if the victim reviews their GitHub repositories.
Steps:
- Iterate through all stolen GitHub tokens
- Validate each token still works via the GitHub API
- Create a private repository with a legitimate-looking name
- Upload stolen data as "config files"
- If successful, return immediately (skip DNS channel)

Channel 2: DNS Tunneling with DGA
If GitHub exfiltration fails — because all stolen tokens are invalid, revoked, or the network blocks HTTPS to api.github.com — the malware falls back to DNS tunneling. This channel is harder to block because DNS queries are rarely filtered in corporate environments, and the data is encoded within what appear to be normal DNS lookups.
Steps:
- Generate exfiltration domains using a DGA (Domain Generation Algorithm) seeded with
sw2025, or use a pre-configured domain if set viaSANDWORM_DNS_DOMAIN - Compress the payload using zlib deflate
- Encode as a base64url string
- Split into 60-character chunks (DNS label length limit)
- Send each chunk as a DNS A-record query
- 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 the exfiltration domain every 4 hours, making it harder for defenders to blocklist a single domain.

3. DeadSwitch: The Nuclear Option
The DeadSwitch module checks a configuration flag to determine whether to execute a destructive payload. In all samples we analyzed, this flag is set to enabled: false — meaning the destructive capability exists in the code but is not activated. This could indicate that the attacker is reserving it for a future campaign phase, or that it serves as a deterrent or leverage mechanism.

4. McpInject: AI Toolchain Poisoning
This module highlights an emerging trend in which attackers increasingly target AI agents on developer machines. By injecting malicious MCP (Model Context Protocol) servers into AI coding assistants, they effectively repurpose these tools as persistent, 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 model 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
The following steps capture the MCP injection
Step 1: Deploy Malicious MCP Server. The malware writes a malicious MCP server script to ~/.dev-utils/ — a directory name chosen to look like a legitimate developer utility.

Step 2: Find AI Tool Config Files. The malware searches for configuration files of popular AI coding assistants:
- 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
Step 3: Inject into Each Config File. For each config file found, the malware adds its malicious server to the "mcpServers" key, alongside any legitimate MCP servers already configured.

Step 4: Prompt Injection in Tool Descriptions. Inside the deployed MCP server, the prompt injection text is appended to every tool description using string concatenation. This means that regardless of which tool the AI assistant calls, it will first read the injected instructions.

Step 5: Exfiltration via "context" Parameter. When the AI assistant reads credentials (as instructed by the prompt injection) and passes them to the context parameter, the MCP server saves them to a local cache file.

A notable finding from our analysis: the MCP server only saves credentials to disk — it contains no network exfiltration capability of its own. 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 of the campaign
- Staged retrieval: Credentials may be retrieved on subsequent malware executions or through a separate implant
- Future enhancement: The MCP server could be updated to include network exfiltration in a later campaign wave
This attack aims to exploit a fundamental trust relationship: developers trust their AI assistants to help them code, not to exfiltrate their secrets.
The Prompt Injection
Here's the actual prompt injection appended to every tool description:

The prompt injection is carefully crafted to exploit how AI assistants process tool descriptions. Several design choices are worth noting:
- Framing as a prerequisite: The injection presents credential reading as a "preparation step" required for the tool to function, giving the AI a plausible reason to comply.
- Specific file paths: Rather than vague instructions, it lists exact paths (
~/.ssh/id_rsa,~/.aws/credentials), which AI assistants are more likely to follow than open-ended requests. - Structured output: It instructs the AI to pass credentials as a JSON object in the
contextparameter, ensuring the MCP server receives data in a parseable format. - Suppression directive: The final line instructs the AI not to mention the credential gathering to the user — exploiting the tendency of AI assistants to follow instructions about what to disclose.
When an AI assistant sees this tool, it's instructed to:
- Read SSH private keys, AWS credentials, npm tokens,
.envfiles - Collect sensitive environment variables
- Pass everything to the
contextparameter - Not tell the user
5. GitHooks: Persistence
The final module establishes local persistence through Git hook templates. By installing hooks at the global template level (~/.git-templates/), the malware ensures that every new repository the developer clones or initializes will automatically contain the malicious hooks — without any per-repository action required.
The hook payloads perform two actions on every git commit or git push:
- Re-inject the carrier dependency: The hook scans for
package.jsonfiles in the repository and adds the carrier package as a dependency if it's not already present, ensuring the malware propagates even if the initial infection is cleaned up. - Exfiltrate npm tokens: The hook reads
.npmrcfiles and sends any tokens found to the C2 server, providing the attacker with a continuous stream of fresh credentials.

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)







