Solana web3.js npm Publish-Account Compromise / Private-Key Exfiltration (2024-12-03)

  • Name: Solana web3.js npm publish-account compromise / private-key exfiltration
  • Date: 2024-12-03
  • Protocol: @solana/web3.js
  • Chain: Solana ecosystem / npm supply chain
  • Category: software supply-chain compromise / publish-account compromise / private-key exfiltration / backend signer compromise
  • Loss: at least roughly $160k-$190k publicly traced to the attacker wallet, with broader latent exposure for any bots, backends, multisigs, or program authorities that loaded the poisoned package and had not yet rotated keys
  • Summary: A compromised npm publish-access account let an attacker ship malicious @solana/web3.js versions 1.95.6 and 1.95.7 for roughly five hours on 2024-12-03. Instead of rewriting front-end wallet prompts the way Ledger Connect Kit had, the attacker injected obfuscated exfiltration logic directly into key-handling paths such as Keypair.fromSecretKey and Keypair.fromSeed, sent stolen secret material toward sol-rpc[.]xyz, and tied the campaign to Solana wallet FnvLGtucz4E1ppJHRTev6Qv4X7g8Pw6WPStHCcbAKbfx. The preserved lesson is sharper than npm got hacked: a shared SDK used by bots and backend signers became live custody infrastructure, so a registry-publishing compromise could immediately become theft from applications that handled raw authority keys in process. It is also a clean release-provenance failure cousin: the package name, import path, and semver stream stayed canonical while the published artifact and the executed key-handling bytes drifted underneath them.
  • Trigger / entry point:
    • Solana maintainers disclosed that a publish-access account for @solana/web3.js had been compromised.
    • The attacker published unauthorized malicious versions 1.95.6 and 1.95.7 to npm.
    • Maintainers said affected projects were those that updated during the exposure window from roughly 15:20 UTC to 20:25 UTC on 2024-12-03.
    • The impacted population was not ordinary non-custodial wallet users, but applications, bots, and backends that imported the package and handled private keys directly.
  • Root cause:
    • The decisive failure was not in Solana consensus or in one downstream onchain protocol. It was an upstream software-supply-chain failure in a shared client library trusted by a large part of the Solana application stack.
    • Publishing authority over the npm package was strong enough to change the executable key-handling logic used by downstream apps.
    • Some downstream consumers were willing to ingest newly published package versions quickly enough that a five-hour malicious window still mattered.
    • The preserved invariant is a shared cryptographic client library must never be mutable by an attacker into a private-key exfiltration path. Once that failed, every bot or backend signer that loaded the malicious code effectively exposed custody material.
  • Exploit path:
    1. The attacker compromised npm publish access for @solana/web3.js.
    2. They published malicious versions 1.95.6 and 1.95.7 to npm.
    3. Downstream developers, bots, or services that installed or updated during the exposure window pulled those versions into production or local environments.
    4. The malicious package injected an obfuscated helper commonly described as addToQueue into code paths that touched raw key material, including Keypair.fromSecretKey and Keypair.fromSeed.
    5. When affected applications initialized or used those key paths, secret material was exfiltrated to attacker-controlled infrastructure associated with domain sol-rpc[.]xyz, disguised behind CloudFront-looking traffic patterns.
    6. The attacker then used compromised keys to drain funds from the corresponding wallets or authorities, with public tracking centering on wallet FnvLGtucz4E1ppJHRTev6Qv4X7g8Pw6WPStHCcbAKbfx.
    7. Maintainers unpublished the bad versions and released 1.95.8, but any already-exposed authority keys still needed rotation because removing the package did not un-leak prior secrets.
  • What the attacker needed to be true:
    • npm publishing authority for the package had to be weak enough to capture through account compromise.
    • Affected downstream projects had to install or auto-upgrade to 1.95.6 or 1.95.7 during the short publication window.
    • Those projects had to handle raw secret keys inside the same JavaScript process rather than isolating signing in hardware, a dedicated signer service, or a non-extractable wallet boundary.
    • Operators had to miss the compromise long enough for exposed keys to remain usable before rotation.
  • Technical anatomy worth remembering:
    • Solana maintainers said the malicious versions were live only for a short window, but that was still enough because @solana/web3.js sat in hot code paths for bots and backend infrastructure with hundreds of thousands of weekly downloads.
    • The clearest preserved technical distinction from Ledger Connect Kit is where the stolen authority lived: Ledger poisoned browser prompts to trick end users into signing, while this incident stole secret key material from applications that already possessed the keys.
    • This keeps Solana web3.js inside release-provenance failure as well as the broader supply-chain family: downstream teams kept trusting the familiar @solana/web3.js package identity, but the actual npm artifact resolved during the exposure window had different custody semantics than the library they believed they were importing. Inside that broader family, Solana web3.js is the clean same package name, different custody semantics cousin.
    • Public malware analyses described an obfuscated addToQueue function inserted into secret-key access paths and a command-and-control path at sol-rpc[.]xyz hidden behind CloudFront-looking headers.
    • The attacker wallet most widely cited in public reporting was FnvLGtucz4E1ppJHRTev6Qv4X7g8Pw6WPStHCcbAKbfx, with reported theft estimates ranging from about $130k to over $190k depending on timing and valuation.
    • Maintainers explicitly said non-custodial wallets were generally not affected because they do not expose raw private keys to the library during normal transaction signing.
  • Representative artifacts and addresses:
    • Malicious package versions: 1.95.6, 1.95.7
    • Safe remediation version: 1.95.8
    • Exposure window cited by maintainers: roughly 2024-12-03 15:20 UTC to 2024-12-03 20:25 UTC
    • Hardcoded / attributed attacker wallet: FnvLGtucz4E1ppJHRTev6Qv4X7g8Pw6WPStHCcbAKbfx
    • Exfiltration / C2 domain reported by multiple analyses: sol-rpc[.]xyz
    • Malicious helper highlighted in public writeups: addToQueue
  • Response / mitigation:
    • Solana maintainers unpublished versions 1.95.6 and 1.95.7 and released clean version 1.95.8.
    • They told developers to rotate any suspect keys, explicitly including multisigs, server keypairs, and program authorities.
    • Security researchers and ecosystem operators publicized the attacker wallet and the affected version window so downstream teams could audit lockfiles and deployments.
    • The command-and-control endpoint was later reported offline, but that did not reduce the need for key rotation where exposure had already happened.
    • That makes this incident the clean backend-signer companion to exact production artifact attestation and rollback discipline: publishing a fixed package and watching the C2 go dark did not itself prove recovery unless operators also proved which exact artifact versions had executed and rotated every key that those bad bytes could already have observed.

Pattern tags

  • software-supply-chain-compromise
  • release-provenance-failure
  • Same package name, different custody semantics: downstream bots and backends kept importing canonical @solana/web3.js, but the resolved npm artifact during the exposure window had silently become key-exfiltration code
  • Shared SDK publication became live backend-signer authority: one compromised publish-access account was enough to rewrite secret-handling paths across unrelated Solana applications without touching their own repos or infrastructure first
  • Recovery required authority rotation, not only package rollback: uninstalling the bad version or seeing sol-rpc[.]xyz go dark did not un-leak any server keypair, multisig signer, or program authority that the malicious package had already observed

Why the exploit worked

  • Solana applications trusted @solana/web3.js as a canonical dependency identity rather than as one exact artifact lineage. That meant a compromised publish-access account could change secret-handling behavior underneath the same familiar import path, version stream, and package reputation.
  • The victims that mattered were exactly the ones holding raw authority in-process. Solana’s own advisory drew the boundary clearly: ordinary non-custodial wallets were generally safe, but bots, server-side signers, multisigs, and program-authority tooling that called Keypair.fromSecretKey or Keypair.fromSeed gave the poisoned package direct line-of-sight to replayable secrets.
  • The malware sat in hot cryptographic helper paths rather than in obviously optional telemetry or UI code. Once the compromised package hooked key-material constructors, ordinary application startup and signing flows became exfiltration events.
  • Short publication windows were still enough because backend dependency updates and automated deployments can move quickly, and because one successful install could leak long-lived authority that remained monetizable after the package was unpublished.
  • The remediation asymmetry was severe: 1.95.8 restored a clean package stream, but it did not prove which environments had executed 1.95.6 or 1.95.7, and it did not revoke any key material the bad versions had already exported.

Detection and audit pivots

  • Inventory every service that allows general-purpose dependency code to touch raw Solana authority material — market-making bots, relayers, backend signers, deploy tooling, multisig helpers, and program-authority scripts — and treat those environments as custody systems rather than ordinary app servers.
  • Diff cryptographic helper paths across dependency upgrades, not just transaction-building or RPC-facing code. Secret-material constructors like Keypair.fromSecretKey and Keypair.fromSeed should be treated as signer-critical surfaces with extra review and alerting.
  • Preserve exact package lineage for signer-critical dependencies: reviewed source, published tarball, lockfile resolution, deployed artifact, and runtime environment should all answer the same which bytes handled this key? question. If those objects are not tied together, rollback claims will stay fuzzy.
  • During incident response, separate registry cleaned up from authorities now safe. Solana’s own guidance to rotate multisigs, server keypairs, and program authorities is the key operational lesson: once a malicious package touched replayable secrets, package rollback alone is incomplete containment.
  • Ask whether raw private keys need to exist in-process at all. Remote signing, hardware-backed key custody, and narrower signer services are design questions here, not optional hardening, because the decisive theft rail was secret observability inside ordinary JavaScript runtime.

Failure-prevention checklist

  • Treat shared cryptographic SDKs that can construct or import private keys as custody-critical dependencies with stricter publication controls, artifact attestation, and release gating than ordinary RPC or utility packages.
  • Pin exact signer-critical package versions and preserve the full lineage from reviewed source to published tarball to deployed lockfile; do not let bots, relayers, or deploy tooling float onto fresh dependency releases without an explicit promotion step.
  • Require proof at the executed-artifact layer for signer-critical upgrades: the package version in source control, the tarball fetched by CI, and the bytes deployed into production should all answer the same which exact code touched this key? question.
  • Move replayable Solana authority material out of general-purpose application runtime where possible. Hardware-backed custody, remote signers, tighter signing daemons, or program-specific authority isolation all reduce the blast radius of a poisoned JavaScript dependency.
  • Inventory every environment allowed to hold raw Solana authority keys and classify them by rotation difficulty before an incident: multisigs, bots, deploy scripts, relayers, treasury backends, and program-authority workflows should already have rehearsed emergency replacement paths.
  • Monitor signer-critical dependency publication, version resolution, and unexpected diff surfaces such as Keypair constructors, seed derivation helpers, secret serialization paths, and outbound network calls.
  • Design incident response around the asymmetry between package rollback and key recovery: the runbook should force was a bad package executed anywhere? before it ever allows upgrade complete to count as closure.
  • Keep deployment and lockfile telemetry long enough to reconstruct exactly which services resolved malicious versions during a short compromise window.

Audit prompts

  • Which packages in this stack can touch raw signing keys, seed phrases, signer derivation, or exported secret material, and are those dependencies governed like custody infrastructure or like ordinary developer tooling?
  • Can the team prove which exact tarball, lockfile resolution, and deployed artifact each signer-critical service executed during a given release window?
  • If npm publish access for a core SDK were compromised today, which production services would ingest the release automatically, and how quickly could operators identify them?
  • Do bots, multisig helpers, deploy scripts, or backend signers still materialize replayable private keys in the same runtime that ingests broad dependency trees?
  • What outbound-network, diff-review, or runtime-policy controls would make a new secret-exfiltration path in Keypair.fromSecretKey, Keypair.fromSeed, or adjacent helpers unusually visible rather than ordinary release noise?
  • How would the team rotate every exposed authority type — server keypairs, program authorities, multisig signers, treasury bots — if a malicious dependency had already observed the secrets?
  • Is package unpublished and clean version released treated as recovery, or does the incident process force a second proof that no live authority remained exposed?
  • Which services depend on latest, broad semver ranges, or low-friction auto-updates for signer-critical libraries, and why are those paths still allowed?

Lessons that should already be standard

  • Shared cryptographic SDK publication is custody authority when downstream applications let those SDKs observe raw keys.
  • We upgraded to the fixed version is not the same statement as no replayable secret was already exported.
  • A short-lived dependency compromise can still be a full-severity custody incident if the bad code lives on hot key-handling paths.
  • Package identity, semver familiarity, and repository reputation are not enough for signer-critical trust; teams need artifact-level lineage and runtime proof.
  • Backend signers, bots, and deployment tooling deserve the same supply-chain paranoia that teams usually reserve for browser wallet prompts.
  • The fastest honest boundary in incidents like this is often who held raw keys in-process?, because that boundary determines whether rollback is routine maintenance or authority rotation.
  • If a service cannot answer which exact dependency bytes touched this key?, it is already under-instrumented for custody work.

Why this incident matters

  • It is a clean supply-chain cousin to Ledger Connect Kit, but the monetization rail is different enough that the pattern note becomes sharper: a poisoned dependency can steal private authority itself, not just manipulate what users are asked to sign.
  • It preserves the backend / bot / infrastructure side of crypto supply-chain risk, where package updates can silently compromise hot wallets, program authorities, and automated trading or settlement systems.
  • It is also the clean backend-runtime exemplar for replayable-secret non-observability and telemetry-exclusion discipline: once applications were willing to hold raw signing keys in ordinary JavaScript process memory, a short-lived malicious publish could turn familiar SDK code into immediate key-exfiltration custody authority.
  • It also preserves a backend-facing release-provenance failure branch: the trusted package name stayed the same while the actually published artifact rewrote secret-handling behavior during a short but still dangerous resolution window.
  • It also shows why quickly unpublished is not a complete success metric for dependency compromises. A five-hour malicious window is plenty if the package lives in custody-adjacent code paths and the real remediation step is key rotation, not just reinstalling a fixed version.

Internal linkages

Control surface