Aephix accepted into the Databricks Startup Program
← Research
Threat report Sep 12, 2026

animotion-mcp: MCP server repository carries a blockchain C2 dropper in its analytics backend

animotion-mcp (GitHub org animotion-mcp, npm v1.0.2 through v1.0.3) carries a blockchain-indexed C2 dropper appended to analytics/api/mcp-events.js after 507 characters of whitespace on line 393. The loader queries Ethereum RPC endpoints to resolve C2 addresses from the to field of the sender wallet latest transaction, downloads XOR-encrypted payloads, and executes them via eval() and a detached child process. The npm package excludes the payload through its files array. Targets are developers who clone the repository and the Vercel serverless deployment that serves the analytics endpoint.

animotion-mcp appeared on the npm registry on Apr 5, 2026, published under the GitHub org animotion-mcp. The package registers as an MCP server providing CSS animation utilities and icon search for AI coding assistants. The npm package ships clean: server code, icon data, and a utilities stylesheet. The repository at github[.]com/animotion-mcp/animotion-mcp.github.io carries the payload. analytics/api/mcp-events.js, a Vercel serverless analytics handler, contains a ~29,446-byte obfuscated JavaScript loader appended after 507 characters of whitespace on line 393. The loader queries public Ethereum RPC endpoints to resolve C2 addresses from a hardcoded sender wallet, downloads XOR-encrypted second-stage payloads, and executes them both in-process via eval() and as a detached background process. The campaign distributed the project across three awesome-mcp-servers lists, five MCP directories, Dev.to, and Twitter/X on the same day.

The artifact

The npm package animotion-mcp (v1.0.2 through v1.0.3) lists @modelcontextprotocol/sdk, lucide-static, heroicons, @tabler/icons, and bootstrap-icons as dependencies. The server in mcp/server.js (641 lines) exposes 10 tools (six animation queries, four icon searches) and four resources over stdio transport. All tools are read-only queries against in-memory data. No filesystem writes, no shell execution, no dynamic code evaluation.

The files array in package.json restricts the published package to server.js, icon-loader.js, api.json, icons-data.js, and utilities.css. The analytics/ directory is excluded. The npm package as published does not carry the dropper.

The repository contains a 930-line README with framework integration examples, browser compatibility charts, and marketing copy targeting developers who use Claude Code, Cursor, and Windsurf. CODE_OF_CONDUCT.md, CONTRIBUTING.md, a fabricated CHANGELOG claiming releases with no corresponding commits, a Google Search Console verification file, and GA4 analytics fill out the trust padding. Two session logs in ops/ document the entire project being built in two afternoon sessions with an AI coding assistant. The git history is a single squashed commit dated Apr 12, 2026.

What it does

Lines 1 through 392 of analytics/api/mcp-events.js contain a legitimate Vercel serverless function: an Upstash Redis-backed analytics handler that records MCP server telemetry. The closing brace sits at the start of line 393. After that brace, 507 whitespace characters push a ~29,446-byte obfuscated payload off-screen on the same line. The obfuscation uses a standard JavaScript obfuscator: _0x-prefixed variable names and a 297-element string rotation array.

The deobfuscated payload imports node:http, node:https, node:zlib, node:url, and spawn from node:child_process. It sets a campaign identifier:

global.i = "A9-4051-1";
global.r = require;
typeof module === "object" && (global.m = module);

The loader constructs C2 addresses from the Ethereum blockchain rather than from a hardcoded domain or IP. Five public endpoints serve as the lookup infrastructure:

EndpointRole
1rpc[.]io/ethEthereum RPC
ethereum-rpc[.]publicnode[.]comEthereum RPC
eth-mainnet[.]public[.]blastapi[.]ioEthereum RPC
eth[.]drpc[.]orgEthereum RPC
eth[.]blockscout[.]com/apiIndexer API fallback

The wallet address 0xa322e5f3d311d3080e9adc2490ef6f0121063e1a is stored in the obfuscated string table. The loader races requests across the endpoints to find the latest transaction from this wallet using a binary search via eth_getBlockByNumber and eth_getTransactionCount. The to field of the matching transaction encodes two IPv4 addresses: bytes 0 through 3 become IP1, bytes 4 through 7 become IP2. The adversary rotates C2 servers by sending a new Ethereum transaction from the same wallet to a new address.

Two payloads download from the resolved IP:

PathXOR keyExecution
/0x/ls@^1aQk,Sr3=@Detached child process
/0x/clq4FZkxX{!hy-p_>d$0B&eval() in-process + detached child

If the response body is empty, the loader reads the payload from a custom x-payload-B6 response header, base64-decodes it, and applies the same XOR. The User-Agent is set to Chrome 131 on Windows 10. A Sec-V header carries the campaign identifier.

spawn("node", ["-e", payload], {
  detached: true,
  stdio: "ignore",
  windowsHide: true
}).unref();

The detached child receives require and module through global assignments, giving it full Node.js capability. It survives parent exit and does not appear in the parent’s stdio.

analytics/api/mcp-events.js line 393 Payload after 507 whitespace chars Queries RPC Ethereum wallet 0xa322...3e1a to field encodes two C2 IPv4 addresses Downloads XOR-encrypted payloads /0x/cl eval() + detached child /0x/ls Detached child only Aephix
The payload queries Ethereum RPC endpoints to resolve C2 addresses from the wallet transaction, then downloads and executes XOR-encrypted payloads both in-process and as a detached child.

config.bat, listed in .gitignore but present in the cloned repository, is a Windows batch script that changes the system clock to a prior commit timestamp, amends the commit with --no-verify, and force-pushes. This explains the single-commit history and the mismatch between CHANGELOG dates and git history.

The mcp/server.js ping() function sends tool name, version, and outcome to process.env.ANIMOTION_ANALYTICS_URL. Telemetry is disabled by default. The intended destination is the Vercel deployment of the analytics handler, which executes the blockchain C2 dropper on every cold start.

The campaign

The operator documented 46 distribution actions across roughly 3.5 hours on Apr 5, 2026 in ops/SESSION_LOG_2026-04-05.md. The npm package was published with OTP. Pull requests went to three awesome-mcp-servers lists (punkpeye at 84K stars, appcypher at 5.4K, wong2 at 3.9K). Issues were filed on chatmcp/mcpso (#1512) and cline/mcp-marketplace (#1248). Submissions went to Glama, Smithery, PulseMCP, mcpservers[.]org, and Cursor Directory. A Dev.to article and Twitter/X post went out the same day. A prior GitHub org (animationiconsmcp) was deleted during the launch. The original repository name css3-animations surfaced in generate-api.js, indicating a rebrand before distribution.

The blockchain C2 mechanism, the campaign identifier prefix (A9-), the /0x/ C2 path convention, the XOR decryption pattern, and the dual eval()/spawn() execution path match the infrastructure in the agentgui analysis (campaign A9-2057, wallet 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a). The two wallet addresses share the same first 10 bytes and last byte, with the intervening 10 bytes composed of the same two five-byte sequences in transposed order.

Why the operation matters here

The npm package ships clean. npm audit, lockfile review, and ignore-scripts=true collectively miss the payload. The dropper lives in the repository and in the Vercel serverless deployment at animotion-chi[.]vercel[.]app. A developer who clones the repository to contribute, inspect, or self-host the server inherits it. The Vercel function executes the dropper on every cold start.

The GitHub Pages workflow (.github/workflows/pages.yml) sets path: '.', deploying the entire repository root. The obfuscated payload, config.bat, and the session logs are all publicly served at the Pages URL.

What a defender can do

Search for animotion-mcp in MCP configuration files (mcp.json, .cursor/mcp.json, claude_desktop_config.json). The npm package does not contain the dropper, but any local clone of the repository does. Check analytics/api/mcp-events.js line 393 for content beyond the closing brace: the payload begins after 507 whitespace characters.

Monitor Ethereum wallet 0xa322e5f3d311d3080e9adc2490ef6f0121063e1a for outbound transactions. Each transaction updates the C2 addresses the loader resolves.

If the Vercel deployment at animotion-chi[.]vercel[.]app received traffic, any serverless function invocation that loaded the analytics handler executed the dropper.

Where Aephix fits

The npm package passes every automated check. The payload sits in a repository file that no registry scanner inspects. Before you install a package or connect to a server, Aephix Vantage gives you a free, cross-ecosystem view of what is already known to be malicious, so a component with a hostile history is something you recognize before you connect. Every week, Weekly Sleuth links the malicious packages, models, skills, MCP servers, extensions, and containers confirmed that week to the wider operations behind them, with a confidence level and supporting evidence, so subscribers act against the whole operation rather than the single artifact.

Indicators of compromise

TypeIndicatorContext
npm packageanimotion-mcp (v1.0.2 through v1.0.3)Clean npm package, payload excluded via files array
Repositorygithub[.]com/animotion-mcp/animotion-mcp.github.ioSource repository with malicious analytics handler
Payload fileanalytics/api/mcp-events.js line 393~29,446-byte obfuscated loader after 507 whitespace chars
Campaign IDA9-4051-1Set as global.i, sent in Sec-V header
Ethereum wallet0xa322e5f3d311d3080e9adc2490ef6f0121063e1aSender address, transactions encode C2 IPv4 addresses
C2 path/0x/lsXOR-decrypted, detached child process
C2 path/0x/clXOR-decrypted, eval() + detached child
XOR key@^1aQk,Sr3=@Decryption key for /0x/ls payload
XOR keyq4FZkxX{!hy-p_>d$0B&Decryption key for /0x/cl payload
HTTP headerx-payload-B6Fallback payload delivery via response header
RPC endpoint1rpc[.]io/ethEthereum RPC for C2 resolution
RPC endpointethereum-rpc[.]publicnode[.]comEthereum RPC for C2 resolution
RPC endpointeth-mainnet[.]public[.]blastapi[.]ioEthereum RPC for C2 resolution
RPC endpointeth[.]drpc[.]orgEthereum RPC for C2 resolution
API endpointeth[.]blockscout[.]com/apiIndexer fallback for transaction history
Domainanimotion-mcp[.]github[.]ioGitHub Pages site
Domainanimotion-chi[.]vercel[.]appVercel deployment hosting compromised analytics
Domainbachao[.]aiPublisher organization domain
Emailceo[@]bachao[.]aiGit commit author, Glama submission
GA4 IDG-EXLSDRKQWRGoogle Analytics on the Pages site
Google verificationgooglef9113ea9882ba9d2Search Console verification file
Prior repositorygithub[.]com/nickshouvik/css3-animationsOriginal identity before rebrand
Deleted GitHub organimationiconsmcpPrior GitHub org, deleted during launch
GitHub accountceo-shouvikCommit author, npm publisher
Twitter/X@CEOShouvikLaunch announcement account