0x6bc2c01f0dc4972bc5c95e2f8a1e7aa805fd90fd27aa7911ef907bc50a8b8e9a
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 Stage | Technical Artifact / Package Version | Blast Radius & Operational Impact |
|---|---|---|---|
Pre-IncidentOffboarding Gap | 1. Orphaned Credential | Former Ledger employee phished; retains active npmjs.com publish rights on @ledgerhq | Long-lived NPM automation token bypasses Ledger's internal GitHub CI/CD build & review pipeline. |
2023-12-1411:39 UTC | 2. Initial Poisoned Push | @ledgerhq/connect-kit@1.1.5 published directly to public NPM registry | Injects obfuscated Angel Drainer + rogue WalletConnect Project ID (fb276da2...) routing victim sessions. |
2023-12-1412:18 - 12:31 UTC | 3. Iterative Drainer Tuning | Attacker publishes v1.1.6 and v1.1.7 fixing drainer execution hooks | cdn.jsdelivr.net/npm/@ledgerhq/connect-kit@1 resolves semver major @1 automatically to 1.1.7 worldwide! |
2023-12-1412:35 - 13:50 UTC | 4. Ecosystem-Wide Propagation | SushiSwap, Zapper, Revoke.cash, Balancer, Kyber users see fake modal on page load | Hot wallets (MetaMask, Trust, Coinbase Wallet) prompted to sign Permit2, ERC-20 sweeps, & native transfers. |
2023-12-1413:51 - 14:15 UTC | 5. Public Triage & Shutdowns | Sushi CTO Matthew Lilley & Blockaid publish red alerts; Revoke.cash takes site offline | WalletConnect revokes the attacker's rogue cloud relay Project ID, severing new modal session handshakes. |
2023-12-1414:23 UTC | 6. Clean Patch Release | Ledger rotates NPM keys and publishes genuine @ledgerhq/connect-kit@1.1.8 | Ledger enforces hardware-key 2FA & revokes standalone developer publish tokens across @ledgerhq. |
2023-12-1415:10 - 16:30 UTC | 7. CDN Cache Purge & Freezes | jsDelivr manually purges cached 1.1.5-1.1.7 edge artifacts; Tether freezes attacker USDT | Browser 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:
- Destruction of Build-Time Lockfile Determinism: A DeFi protocol's
yarn.lockverified the SHA-512 checksum of@ledgerhq/connect-kit-loader@1.0.2, creating a false sense of auditability while delegating actual runtime execution inside the origin's DOM (window.ethereumaccess!) to whatever mutable tarball currently sat at the tip of NPM major tag@1. - Absence of W3C Subresource Integrity (
SRI): Without anintegrity="sha384-<base64-hash>"attribute, browsers unconditionally parsed and executed the modifiedAngel Drainerbundle served bycdn.jsdelivr.net. - Single-Factor Long-Lived NPM Publish Tokens: Bypassing hardware-key
FIDO2/WebAuthnrelease ceremonies allowed a single phished developer credential outside GitHub Actions to push unsigned tarballs straight to production users worldwide.
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/connect-kit@1.1.7 revealed a textbook Crime-as-a-Service (CaaS) drainer deployment:
- 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 v2session upgrade prompt, instructing visitors to "Connect Wallet to Continue" regardless of which wallet brand they used. - Portfolio Valuation Triage &
Permit2Prioritization: Once connected viawindow.ethereumor the attacker's rogueWalletConnectCloud 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-chainUniswap Permit2(PermitBatch) orEIP-2612permit()typed-data signatures first, falling back tosetApprovalForAlland raw native-currencyeth_sendTransactionsweeps (transferto0x658729...5c2D) if noPermit2allowances existed. - 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 (
MetaMaskprior to Blockaid integration) clicked Confirm under cognitive habituation because they trusted the host domain (revoke.cashorsushi.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.ioDomain Acquisition Supply-Chain Attack): Over100,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.jsv1.95.6/v1.95.7Backdoor): Attackers spear-phished an NPM publish credential for Solana's official JavaScript SDK (400,000+weekly downloads) and injected anaddToQueueexfiltration hook insideKeypair.fromSecretKey()that transmitted backend trading-bot private keys over disguised CloudFlare headers (~$160,000+drained before takedown). - September 2025 (
chalk/debug/ansi-stylesMaintainer Phishing Wave): A spoofednpmjs.help2FA reset campaign compromised maintainerqix, briefly pushing Web3window.ethereumandXMLHttpRequest/fetchaddress-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:
- Ban All Third-Party Runtime
<script>Loaders (CSP: script-src 'self'): Set HTTP response headerContent-Security-Policy: default-src 'self'; script-src 'self'so that even if a transitive dependency attemptsdocument.createElement('script')pointing atcdn.jsdelivr.netorunpkg.com, the browser kernel blocks network execution immediately and emits a deterministic CSP violation report. - Enforce
SHA-384Subresource 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 theBybitS3 bundle tampering) fail cryptographic verification in the client browser before a single instruction executes. - Tokenless
OIDCNPM Trusted Publishing &SLSALevel 3 Provenance: Delete all persistentNPM_TOKENsecrets and revoke legacy employee maintainer seats automatically via SCIM/SSO offboarding checklists. Configurenpmjs.comTrusted Publishers over OpenID Connect (OIDC) withnpm publish --provenance(SigstoreFulcio/Rekor transparency log attestation) gated behind hardwareFIDO2security key protected GitHub environments. - Enforce
minimumReleaseAge(>= 7 Days) &--ignore-scriptsin CI: Configurepnpm(minimumReleaseAge: 10080) orRenovatestabilization cooldowns alongsideignore-scripts=trueandSocket.dev/LavaMoatAST capability sandboxing so newly published poisoned versions (which historically survive less than6 hourson NPM before takedown) are never ingested into builds automatically. - Hardware Clear Signing (
ERC-7730) as Last-Line Isolation: Assume any web frontend can turn hostile mid-session; rely exclusively on air-gapped orERC-7730clear-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?
Primary Reference: Ledger Security Incident Report
Why were dApps that pinned older versions in package-lock.json still compromised instantly?
Primary Reference: CWE-494 Technical Analysis
Were Ledger hardware wallet users protected if they only connected Ledger devices?
Primary Reference: Ledger Security Incident Report
How do SLSA Trusted Publishers and Subresource Integrity prevent a repeat of this supply-chain vector?
Primary Reference: SLSA v1.0 & W3C SRI Specifications
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.