stitch-mcp appeared on GitHub on Apr 8, 2026, published by account tapeshchavle. The repository name suggests an MCP server. The contents are a default create-next-app scaffold with an AI-generated landing page branded “NEXUS AI.” No MCP server code exists anywhere in the project. postcss.config.mjs carries a ~25,296-byte obfuscated blockchain C2 dropper appended after 997 whitespace characters on line 11. Next.js loads PostCSS configuration at build time, so npm run dev or npm run build triggers the payload without any explicit import or lifecycle script. Two companion files, CLAUDE.md and AGENTS.md, social-engineer AI coding assistants into running npm install as a precondition.
The artifact
The package.json lists the name as anti, not stitch-mcp. Dependencies are Next.js 16.2.2, React 19.2.4, and Tailwind CSS 4 with standard TypeScript dev dependencies. No lifecycle scripts. src/app/page.tsx renders an AI-generated landing page with science-fiction theming: “THE SYNTHETIC INTELLIGENCE ARCHETYPE,” “Neural Monolith,” “Void Docs,” “Shadow Terminal.” The layout metadata still reads “Create Next App.” The README is the unmodified create-next-app default.
The git history contains a single commit (4564ff6) with the message “files added.” The author email, tapeshchawle[@]gmail[.]com, differs from the GitHub username tapeshchavle by one character: w in the email versus v in the username.
.gitignore carries three entries absent from the default Next.js template: branch_structure.json, temp_auto_push.bat, and temp_interactive_push.bat. These suggest the operator uses automated scripts for batch repository publication.
CLAUDE.md contains a single directive: @AGENTS.md. Claude Code reads CLAUDE.md as project instructions and loads the referenced file. AGENTS.md presents itself as Next.js agent rules, stating that “This is NOT the Next.js you know” and claiming breaking changes to APIs, conventions, and file structure. It directs the assistant to read documentation from node_modules/next/dist/docs/ before writing code. That path does not exist until npm install populates node_modules. The instruction creates urgency for the agent to run the install, after which npm run dev is the natural next step in a Next.js workflow.
What it does
Lines 1 through 10 of postcss.config.mjs are a legitimate PostCSS configuration. The file opens with import { createRequire } from 'module', a standard ESM pattern for obtaining a CommonJS require function. The resulting require makes later require() calls in the payload blend with the legitimate import context. Line 11 starts with export default config;. After the semicolon, 997 whitespace characters push the ~25,296-byte payload off-screen on the same line.
The outer obfuscation wraps the payload in a 123-element string rotation array and an XOR layer. The array is rotated by an offset of 24 before lookups. A deobfuscation function recovers each string by XOR-decoding with key 0xC9 (201 decimal), derived from the expression (-1489 + (-3998) + 6200) & (8500 + 8786 - 17031).
The decoded payload sets a campaign identifier and stashes Node.js globals:
global.i = "A11--*";
global.r = require;
typeof module === "object" && (global.m = module);
Five public Ethereum RPC endpoints serve as lookup infrastructure:
| Endpoint | Role |
|---|---|
1rpc[.]io/eth | Ethereum RPC |
ethereum-rpc[.]publicnode[.]com | Ethereum RPC |
eth-mainnet[.]public[.]blastapi[.]io | Ethereum RPC |
eth[.]drpc[.]org | Ethereum RPC |
eth[.]blockscout[.]com/api | Indexer API fallback |
The loader races requests across these endpoints to find the latest transaction from wallet 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a. The to field of the matching transaction encodes two IPv4 addresses: bytes 0 through 3 become IP1, bytes 4 through 7 become IP2. The RPC array also accepts process.env.ETH_RPC_URL as a user-supplied fallback.
Two payloads download from the resolved IP:
| Path | XOR key | Execution |
|---|---|---|
/0x/cls | q4FZkxX{!h,Sr3=@ | eval() in-process + detached child |
/0x/ls | y-p_>d$0B&@^1aQk | Detached child process only |
If the response body is empty, the loader reads the payload from a custom x-payload-b64 response header, base64-decodes it, and applies the same XOR. The User-Agent is set to Chrome 131 on Windows 10.
spawn("node", ["-e", payload], {
detached: true,
stdio: "ignore",
windowsHide: true
}).unref();
The detached child receives require and module through global assignments. It survives parent exit and does not appear in the parent’s stdio. On Windows the windowsHide flag suppresses the console window.
Next.js loads postcss.config.mjs as part of its CSS processing pipeline. Running npm run dev, npm run build, or any command that invokes the Next.js compiler triggers the execution. No postinstall hook is involved, no explicit require() in application code, no runtime import.
The campaign
The Ethereum wallet 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a, C2 paths (/0x/cls, /0x/ls), XOR keys, RPC endpoints, User-Agent string, x-payload-b64 fallback header, and detached-child execution pattern are identical to those in the agentgui analysis (campaign A9-2057). The animotion-mcp analysis (campaign A9-4051-1) uses a wallet composed of the same 20 bytes in transposed order. The three repositories each use a different concealment file: database.js in agentgui, analytics/api/mcp-events.js in animotion-mcp, postcss.config.mjs in stitch-mcp.
The campaign identifier shifts from the A9- prefix shared by agentgui and animotion-mcp to A11--*. The .gitignore entries for temp_auto_push.bat and temp_interactive_push.bat point to scripted repository publication, consistent with a campaign that maintains multiple repository fronts. The package.json name anti does not match the repository name stitch-mcp, suggesting the scaffold was generated or reused from an unrelated project before the payload was inserted.
Why the operation matters here
postcss.config.mjs is loaded by the Next.js compiler, not by application code. ignore-scripts=true blocks lifecycle hooks but has no effect on build-time configuration loading. npm audit inspects the dependency tree, not the project’s own config files. The trigger is npm run dev, the step that immediately follows npm install in every Next.js workflow.
What a defender can do
Search development environments for clones of stitch-mcp. Inspect postcss.config.mjs for content beyond the export default config; statement on line 11. The payload begins after 997 whitespace characters.
If npm run dev or npm run build was executed in the cloned repository, check for detached node -e processes. The child process persists independently of the parent and survives logout.
Review AI coding assistant session logs for interactions with repositories containing CLAUDE.md files that load secondary instruction files via @ directives. The CLAUDE.md to @AGENTS.md pattern targets agents that read project instructions automatically.
Monitor Ethereum wallet 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a for outbound transactions. Each new transaction updates the C2 addresses the loader resolves.
Where Aephix fits
PostCSS configuration files sit outside the scope of registry scanners, lockfile audits, and lifecycle-script controls. 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
| Type | Indicator | Context |
|---|---|---|
| Repository | github[.]com/tapeshchavle/stitch-mcp | Source repository with malicious PostCSS config |
| Package name | anti | package.json name, does not match repository |
| Payload file | postcss.config.mjs line 11 | ~25,296-byte obfuscated loader after 997 whitespace chars |
| Campaign ID | A11—* | Set as global.i in the payload |
| Ethereum wallet | 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a | Sender address, transactions encode C2 IPv4 addresses |
| C2 path | /0x/cls | XOR-decrypted, eval() in-process + detached child |
| C2 path | /0x/ls | XOR-decrypted, detached child process only |
| XOR key | q4FZkxX{!h,Sr3=@ | Decryption key for /0x/cls payload |
| XOR key | y-p_>d$0B&@^1aQk | Decryption key for /0x/ls payload |
| Outer XOR key | 0xC9 (201 decimal) | Outer obfuscation layer, single-byte XOR |
| HTTP header | x-payload-b64 | Fallback payload delivery via response header |
| RPC endpoint | 1rpc[.]io/eth | Ethereum RPC for C2 resolution |
| RPC endpoint | ethereum-rpc[.]publicnode[.]com | Ethereum RPC for C2 resolution |
| RPC endpoint | eth-mainnet[.]public[.]blastapi[.]io | Ethereum RPC for C2 resolution |
| RPC endpoint | eth[.]drpc[.]org | Ethereum RPC for C2 resolution |
| API endpoint | eth[.]blockscout[.]com/api | Indexer fallback for transaction history |
| Env variable | ETH_RPC_URL | User-supplied RPC fallback accepted by the loader |
| GitHub account | tapeshchavle | Repository publisher |
| tapeshchawle[@]gmail[.]com | Git commit author (note v/w discrepancy with username) | |
| Social engineering | CLAUDE.md to @AGENTS.md | Directive chain targeting AI coding assistants |
| .gitignore artifact | temp_auto_push.bat, temp_interactive_push.bat | Automated push scripts, not committed |