Recent

↑ ↓ to move ↵ to open esc to close

Ledger Connect Kit NPM & jsDelivr CDN Supply-Chain Injection Post-Mortem

Forensic reconstruction of the December 14, 2023 @ledgerhq/connect-kit NPM token compromise, unpinned CDN auto-loading across DeFi frontends, and SLSA/SRI countermeasures.

Forensic Incident Dossier DT-INC-2023-009
Timestamp (UTC) 2023-12-14T11:39:00Z
Exploit Volume $0.6M USD
Root Cause CWE CWE-494
Attribution Angel Drainer Phishing Affiliate
Affected Chains Ethereum, Arbitrum, Optimism, Polygon, BNB Smart Chain
Verified Tx Hash 0x6bc2c01f0dc4972bc5c95e2f8a1e7aa805fd90fd27aa7911ef907bc50a8b8e9a
Forensic Root-Cause Capsule
On December 14, 2023, an attacker used a phished former Ledger employee's unrevoked NPM automation token to publish malicious versions 1.1.5 through 1.1.7 of @ledgerhq/connect-kit containing Angel Drainer. Because Ledger's loader package dynamically fetched unpinned latest JavaScript from jsDelivr CDN without Subresource Integrity hashes, dozens of innocent DeFi frontends served wallet-draining modals simultaneously for nearly five hours.
Forensic reconstruction plate for DT-INC-2023-009
FORENSIC RECONSTRUCTION // DT-INC-2023-009 (supply-chain-npm-cdn)

Incident Executive Summary: When Lockfile Pinning Fails at Runtime

On December 14, 2023, between 11:39 UTC and 16:30 UTC, the decentralized finance ecosystem experienced its widest-reaching client-side supply-chain compromise to date (DT-INC-2023-009). An affiliate of the Angel Drainer phishing syndicate compromised the NPMJS credentials of a former Ledger employee via a targeted Spear-Phishing link and published three consecutive malicious versions (1.1.5, 1.1.6, and 1.1.7) of @ledgerhq/connect-kit.

What alarmed institutional security teams across SushiSwap, Zapper, Balancer, Revoke.cash, and dozens of Web3 frontends was how the malicious code bypassed build-time package-lock.json and yarn.lock controls. Even dApps that had not run a fresh deployment in weeks began serving live Angel Drainer modals to every visitor instantly! Why? Because Ledger's lightweight integration wrapper (@ledgerhq/connect-kit-loader) had been engineered to inject an unpinned <script src="https://cdn.jsdelivr.net/npm/@ledgerhq/connect-kit@1"> element into the browser DOM at runtime, completely devoid of W3C Subresource Integrity (SRI) cryptographic hashes (CWE-494).

Within a roughly two-hour peak exploitation window before jsDelivr purged CDN edge caches and Tether froze attacker USDT treasuries, $610,000 in user assets across Ethereum, Arbitrum, Optimism, Polygon, and BNB Smart Chain were drained into 0x658729879fCa881D9526480B82aE00EFc54B5c2D (with approximately $150,000+ frozen or immobilized via stablecoin issuer blacklisting). Had the attacker targeted silent safeTxHash replacement solely against institutional treasuries rather than noisy retail WalletConnect popups (the exact tradecraft upgrade executed fourteen months later by DPRK Lazarus in the $1.46B Bybit Safe S3 attack), losses would easily have reached nine figures.


Minute-by-Minute UTC Forensic Timeline (Dec 14, 2023)

Timestamp (UTC)Supply-Chain StageTechnical Artifact / Package VersionBlast Radius & Operational Impact
Pre-Incident
Offboarding Gap
1. Orphaned CredentialFormer Ledger employee phished; retains active npmjs.com publish rights on @ledgerhqLong-lived NPM automation token bypasses Ledger's internal GitHub CI/CD build & review pipeline.
2023-12-14
11:39 UTC
2. Initial Poisoned Push@ledgerhq/[email protected] published directly to public NPM registryInjects obfuscated Angel Drainer + rogue WalletConnect Project ID (fb276da2...) routing victim sessions.
2023-12-14
12:18 - 12:31 UTC
3. Iterative Drainer TuningAttacker publishes v1.1.6 and v1.1.7 fixing drainer execution hookscdn.jsdelivr.net/npm/@ledgerhq/connect-kit@1 resolves semver major @1 automatically to 1.1.7 worldwide!
2023-12-14
12:35 - 13:50 UTC
4. Ecosystem-Wide PropagationSushiSwap, Zapper, Revoke.cash, Balancer, Kyber users see fake modal on page loadHot wallets (MetaMask, Trust, Coinbase Wallet) prompted to sign Permit2, ERC-20 sweeps, & native transfers.
2023-12-14
13:51 - 14:15 UTC
5. Public Triage & ShutdownsSushi CTO Matthew Lilley & Blockaid publish red alerts; Revoke.cash takes site offlineWalletConnect revokes the attacker's rogue cloud relay Project ID, severing new modal session handshakes.
2023-12-14
14:23 UTC
6. Clean Patch ReleaseLedger rotates NPM keys and publishes genuine @ledgerhq/[email protected]Ledger enforces hardware-key 2FA & revokes standalone developer publish tokens across @ledgerhq.
2023-12-14
15:10 - 16:30 UTC
7. CDN Cache Purge & FreezesjsDelivr manually purges cached 1.1.5-1.1.7 edge artifacts; Tether freezes attacker USDTBrowser HTTP cache persistence keeps residual poisoned scripts alive for users until hard refresh (~4.8h window).

Architectural Root Cause: The @ledgerhq/connect-kit-loader Anti-Pattern

Why would a hardware-security manufacturer build a dynamic runtime CDN script loader in the first place? In 2022-2023, Ledger wanted dApp developers to adopt updates to Bluetooth/USB/WalletConnect transport bridges without requiring hundreds of DeFi teams to bump package.json dependencies and redeploy their frontends.

To achieve "zero-effort updates," @ledgerhq/connect-kit-loader executed the equivalent of:

// VULNERABLE ANTI-PATTERN (CWE-494: Unpinned Semver CDN Injection, Zero SRI)
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/@ledgerhq/connect-kit@1/dist/umd/index.js';
script.async = true;
// MISSING: script.integrity = 'sha384-...';
// MISSING: script.crossOrigin = 'anonymous';
document.head.appendChild(script);

This single architectural shortcut violated three foundational web-cryptography invariants simultaneously:

  1. Destruction of Build-Time Lockfile Determinism: A DeFi protocol's yarn.lock verified the SHA-512 checksum of @ledgerhq/[email protected], creating a false sense of auditability while delegating actual runtime execution inside the origin's DOM (window.ethereum access!) to whatever mutable tarball currently sat at the tip of NPM major tag @1.
  2. Absence of W3C Subresource Integrity (SRI): Without an integrity="sha384-<base64-hash>" attribute, browsers unconditionally parsed and executed the modified Angel Drainer bundle served by cdn.jsdelivr.net.
  3. Single-Factor Long-Lived NPM Publish Tokens: Bypassing hardware-key FIDO2 / WebAuthn release ceremonies allowed a single phished developer credential outside GitHub Actions to push unsigned tarballs straight to production users worldwide.
Unpinned NPM/CDN Dynamic Loader Attack Chain vs SLSA + SHA-384 SRI DefenseTwo-tier architectural diagram comparing the vulnerable December 2023 NPM token to jsDelivr @1 runtime injection path against hardened OIDC SLSA Level 3 provenance, offline lockfile bundling, and SHA-384 Subresource Integrity.VULNERABLE PATH (DEC 2023): ORPHANED NPM TOKEN -> UNPINNED JSDELIVR @1 -> DOM DRAINER1. Phished Ex-Staff NPM Tokennpm publish [email protected]Bypasses GitHub CI & 2FA;embeds Angel Drainer payload2. Auto-Resolving Public CDNcdn.jsdelivr.net/.../kit@1Resolves @1 -> 1.1.7 globally;integrity attribute: NONE3. Victim dApp Origin DOMFull window.ethereum accesson Sushi, Zapper, Revoke;$610,000 swept in ~2 hrsHARDENED SUPPLY-CHAIN ARCHITECTURE: OIDC SLSA PROVENANCE + SELF-HOSTED SRI + STRICT CSP1. Tokenless OIDC Releasenpm publish --provenanceSigstore Fulcio/Rekor log +2-person FIDO2 Git tag sign2. Immutable Build Bundlingnpm ci --ignore-scripts7-day cooldown on new versions;zero third-party runtime CDNs3. Browser Cryptographic Gatescript-src 'self'integrity="sha384-..."Tampered bundle blocked!
Figure 1: Contrast between the runtime CDN semver loader exploited in DT-INC-2023-009 and DT.gl's self-hosted SHA-384 SRI and script-src 'self' supply-chain model.

Inside the Poisoned v1.1.7 Payload: How Angel Drainer Hijacked window.ethereum

Deobfuscation of the malicious dist/umd/index.js artifact published in @ledgerhq/[email protected] revealed a textbook Crime-as-a-Service (CaaS) drainer deployment:

  1. DOM Modal Overlay & Forced Session Re-Pairing: Rather than waiting passively for a user to click "Connect Ledger," the injected script rendered an unclosable full-screen modal immediately on page load mimicking a legitimate WalletConnect v2 session upgrade prompt, instructing visitors to "Connect Wallet to Continue" regardless of which wallet brand they used.
  2. Portfolio Valuation Triage & Permit2 Prioritization: Once connected via window.ethereum or the attacker's rogue WalletConnect Cloud Project ID (fb276da2...), Angel Drainer's backend queried the victim's multi-chain token balances (Ethereum, Arbitrum, Optimism, Polygon, BSC), sorted assets in descending USD liquidity order, and constructed gasless off-chain Uniswap Permit2 (PermitBatch) or EIP-2612 permit() typed-data signatures first, falling back to setApprovalForAll and raw native-currency eth_sendTransaction sweeps (transfer to 0x658729...5c2D) if no Permit2 allowances existed.
  3. Why Hardware Clear-Signing Survived While Hot Wallets Failed: Ironically, users connecting an actual Ledger hardware wallet with Blind Signing disabled were largely protected at the final step because the physical Secure Element display clearly showed an unexpected asset transfer or unknown spender address rather than a harmless login signature. By contrast, users running browser hot-wallet extensions without transaction simulation (MetaMask prior to Blockaid integration) clicked Confirm under cognitive habituation because they trusted the host domain (revoke.cash or sushi.com) displayed in their browser address bar!

Transitive Dependency Blast Radius (wagmi & Wallet Aggregators)

A critical lesson for Web3 frontend architects was transitive dependency opacity. Several compromised DeFi teams initially announced they were "unaffected" because @ledgerhq/connect-kit did not appear anywhere in their top-level package.json. Minutes later, community security researchers proved their live production apps were actively serving Angel Drainer! Why? Popular multi-wallet connector libraries (wagmi connectors, Web3Modal, RainbowKit, and Dynamic SDKs) pulled @ledgerhq/connect-kit-loader three or four levels deep inside node_modules. Even if zero users ever clicked the "Ledger" button in the dApp's wallet menu, eager module initialization executed the loader script at bundle startup.


Recurring NPM Supply-Chain Waves (2023 - 2026) & Mandatory Engineering Controls

Far from an isolated anomaly, DT-INC-2023-009 foreshadowed a multi-year wave of JavaScript registry attacks against cryptocurrency infrastructure:

  • June 2024 (Polyfill.io Domain Acquisition Supply-Chain Attack): Over 100,000+ websites loading unpinned <script src="https://cdn.polyfill.io/v2/polyfill.min.js"> had dynamic redirect and payload scripts injected after the legacy CDN domain changed ownership, prompting Cloudflare and Google to issue ecosystem-wide emergency warnings against third-party utility CDNs.
  • December 2024 (@solana/web3.js v1.95.6 / v1.95.7 Backdoor): Attackers spear-phished an NPM publish credential for Solana's official JavaScript SDK (400,000+ weekly downloads) and injected an addToQueue exfiltration hook inside Keypair.fromSecretKey() that transmitted backend trading-bot private keys over disguised CloudFlare headers (~$160,000+ drained before takedown).
  • September 2025 (chalk / debug / ansi-styles Maintainer Phishing Wave): A spoofed npmjs.help 2FA reset campaign compromised maintainer qix, briefly pushing Web3 window.ethereum and XMLHttpRequest/fetch address-replacement clipper malware into foundational JavaScript packages exceeding 2.6 billion weekly downloads.

Every security-critical Web3 application, block explorer, and cryptographic tool (including DT.gl itself) must enforce five deterministic controls:

  1. Ban All Third-Party Runtime <script> Loaders (CSP: script-src 'self'): Set HTTP response header Content-Security-Policy: default-src 'self'; script-src 'self' so that even if a transitive dependency attempts document.createElement('script') pointing at cdn.jsdelivr.net or unpkg.com, the browser kernel blocks network execution immediately and emits a deterministic CSP violation report.
  2. Enforce SHA-384 Subresource Integrity (SRI): Generate deterministic <script type="module" src="/_astro/..." integrity="sha384-..." crossorigin="anonymous"> hashes at build time so unexpected CDN or storage-bucket edits (such as the Bybit S3 bundle tampering) fail cryptographic verification in the client browser before a single instruction executes.
  3. Tokenless OIDC NPM Trusted Publishing & SLSA Level 3 Provenance: Delete all persistent NPM_TOKEN secrets and revoke legacy employee maintainer seats automatically via SCIM/SSO offboarding checklists. Configure npmjs.com Trusted Publishers over OpenID Connect (OIDC) with npm publish --provenance (Sigstore Fulcio/Rekor transparency log attestation) gated behind hardware FIDO2 security key protected GitHub environments.
  4. Enforce minimumReleaseAge (>= 7 Days) & --ignore-scripts in CI: Configure pnpm (minimumReleaseAge: 10080) or Renovate stabilization cooldowns alongside ignore-scripts=true and Socket.dev / LavaMoat AST capability sandboxing so newly published poisoned versions (which historically survive less than 6 hours on NPM before takedown) are never ingested into builds automatically.
  5. Hardware Clear Signing (ERC-7730) as Last-Line Isolation: Assume any web frontend can turn hostile mid-session; rely exclusively on air-gapped or ERC-7730 clear-signing hardware devices that decode exact recipient, contract, and allowance bytes on trusted silicon.

Reproducible Build & CI/CD Verification Checklist for Web3 Frontends

Teams operating decentralized exchange routers, multisig dashboards, bridge interfaces, or client-side cryptographic workbenches can eliminate the entire DT-INC-2023-009 vulnerability class inside thirty minutes by enforcing the following build-pipeline gates:

# 1. Scan lockfiles for any dynamic CDN loader packages or unpinned git/http tarballs
rg -n "connect-kit-loader|cdn\.jsdelivr\.net|unpkg\.com|polyfill\.io" package-lock.json pnpm-lock.yaml src/

# 2. Enforce frozen lockfiles with lifecycle postinstall scripts disabled in CI
pnpm install --frozen-lockfile --ignore-scripts

# 3. Verify every emitted HTML entrypoint enforces self-hosted assets and SHA-384 SRI
rg -n '<script[^>]+src="https?://' dist/

Pairing deterministic static builds (Astro / Vite with zero external <script> origins) with immutable content-addressed mirrors (IPFS / Arweave CIDs published via DNSLink or ENS, audited in Decentralized & Self-Hosted Protocols) and strict HTTP Content-Security-Policy headers (script-src 'self'; connect-src 'none' on offline signing utilities) guarantees that an upstream registry compromise at npmjs.com or PyPI can never silently mutate live code inside user browsers. Whenever interacting with third-party DeFi frontends that have not yet adopted verifiable SRI and CSP isolation, isolate wallet extensions inside dedicated hardened browser profiles and revoke residual Permit2 token allowances immediately after trade settlement. Institutional security teams should additionally maintain a pre-compiled, offline-runnable local emergency bundle of critical recovery utilities (Revoke, Safe CLI, and raw ABI calldata encoders) verified against upstream Git commit signatures so treasury responders never depend on live third-party DNS or CDN infrastructure during an active ecosystem incident.

Frequently Verified Technical Questions

How did the attacker publish malicious @ledgerhq/connect-kit packages even after the phished employee left Ledger?

Although the former employee's corporate identity access was closed upon departure, their NPM account remained listed as a package maintainer or retained an unrevoked granular NPM publish automation token that bypassed interactive two-factor authentication prompts during CLI package uploads outside Ledger's GitHub CI/CD pipeline.

Primary Reference: Ledger Security Incident Report

Why were dApps that pinned older versions in package-lock.json still compromised instantly?

Most DeFi frontends did not install @ledgerhq/connect-kit directly; they installed @ledgerhq/connect-kit-loader with a pinned lockfile hash. However, that loader's runtime JavaScript created a DOM script tag pointing to cdn.jsdelivr.net/npm/@ledgerhq/connect-kit@1 without an exact patch version or SHA-384 integrity attribute, pulling 1.1.5 live into user browsers.

Primary Reference: CWE-494 Technical Analysis

Were Ledger hardware wallet users protected if they only connected Ledger devices?

Users who actually inspected their hardware wallet physical screens before confirming rejected unauthorized clear-signed transfers, whereas most of the 610,000 USD stolen came from MetaMask, Rabby, and WalletConnect hot-wallet users visiting affected dApps like SushiSwap, Zapper, and Revoke.cash and clicking through the fake modal.

Primary Reference: Ledger Security Incident Report

How do SLSA Trusted Publishers and Subresource Integrity prevent a repeat of this supply-chain vector?

NPM Trusted Publishing via OpenID Connect binds package releases cryptographically to a specific signed GitHub Actions workflow with multi-party code review, eliminating long-lived developer publish tokens. On the client side, bundling dependencies at build time with immutable lockfiles and SHA-384 Subresource Integrity tags prevents runtime CDN script mutation.

Primary Reference: SLSA v1.0 & W3C SRI Specifications

Cryptographic & Editorial Revision Ledger

Published: | Last Verified:

  • 2026-11-11: Added comparative analysis against the September 2025 chalk/debug NPM maintainer phishing wave and updated NPM OIDCTrustedPublisher / SLSA Level 3 verification controls.
  • 2026-06-24: Initial DT.gl publication of forensic post-mortem DT-INC-2023-009 reconstructing the December 2023 Ledger Connect Kit supply-chain compromise.