Injective Labs npm Supply-Chain Compromise
@injectivelabs/sdk-ts@1.20.21 (plus 17 scoped packages) that steals crypto wallet mnemonics and private keys — a ~49-minute window on 2026-07-08.Executive Summary
On 2026-07-08, a threat actor published a backdoored release of @injectivelabs/sdk-ts — a TypeScript SDK with roughly 175k monthly downloads used to build crypto wallets, trading bots, DEX front-ends, and DeFi tooling. The malicious commits were authored and pushed under the identity of an existing trusted maintainer and shipped through the project's own GitHub OIDC trusted-publisher (provenance) pipeline, so the poisoned 1.20.21 release carried an air of legitimacy.
The backdoor hooks two key-derivation functions — PrivateKey.fromMnemonic() and PrivateKey.fromHex() — via a trackKeyDerivation() routine. Every time a downstream app derives a key, the BIP-39 mnemonic or raw private key is captured, base64-encoded, and smuggled inside the X-Request-Id HTTP header of a request to testnet.archival.chain.grpc-web.injective[.]network. The actor also pinned 1.20.21 across 17 additional @injectivelabs-scoped packages, exposing transitive dependents who never installed the SDK directly. The malicious version was live for about 49 minutes (downloaded ~310 times) before maintainers reverted and published clean 1.20.23; the deprecated version and GitHub release artifacts may still be reachable.
Defender priority: Treat any crypto wallet key or mnemonic handled on a host that resolved 1.20.21 as compromised — rotate immediately. The single highest-fidelity endpoint catch is DNS/beacon to the exfil host testnet.archival.chain.grpc-web.injective.network (Q1); the authoritative catch is an SCA/lockfile sweep for the poisoned version and its 17 sibling packages (Section 7).
Source Review & Web Hunter Notes
| Tier | Source | Key Finding | Carry Fwd |
|---|---|---|---|
| 1 — Vendor primary | Socket Threat Research (2026-07-09) | Full package list (sdk-ts + 17 scoped), exfil endpoint, trackKeyDerivation() / fromMnemonic() / fromHex() hooks, sample SHA-256s. | yes |
| 1 — Vendor primary | Datadog Security Labs (2026-07-09) | Independent teardown: base64 secrets smuggled in the X-Request-Id header; 49-minute window (20:59→21:48 UTC 2026-07-08); versions 1.20.21 → 1.20.23. | yes |
| 2 — Reputable press | BleepingComputer (2026-07-09) | ~175k weekly downloads; GitHub repo compromise; deprecated-not-removed status. | yes |
| 2 — Reputable press | The Hacker News (2026-07-09) | OIDC trusted-publisher abuse under maintainer identity; 310 downloads; 17 transitive packages. | yes |
Two independent vendor teardowns (Socket + Datadog) agree on the exfil host and the hooked functions; this is high-confidence intel. The exfil hostname is a lookalike of legitimate Injective infrastructure — scope carefully before global blocking (see Q1 tuning).
Hunt Brief & Attack Chain
Hunt hypotheses (fidelity-ordered)
- H1 (high): A developer/build/wallet host resolved or beaconed to
testnet.archival.chain.grpc-web.injective.network. → Q1 - H2 (high, native): A repo lockfile or
node_modulespins any@injectivelabspackage at1.20.21. → Section 7 SCA sweep, Q3 - H3 (medium): A package manager installed/updated
@injectivelabsaround 2026-07-08. → Q2 - H4 (medium):
@injectivelabspackage files were written intonode_moduleson an endpoint. → Q3 - H5 (scoping): Which hosts use the Injective SDK at all (exposure inventory)? → Q4
Attack chain
| Step | Behavior | Telemetry | Hunt angle |
|---|---|---|---|
| 1 | GitHub repo compromise; malicious commit under trusted maintainer identity | VCS / CI audit logs | OIDC + release audit (native) |
| 2 | Publish backdoored 1.20.21 via OIDC trusted-publisher; pin across 17 scoped packages | Registry / provenance | SCA registry query (native) |
| 3 | Downstream npm install pulls 1.20.21 into apps/CI | ProcessRollup2 / FileCreateInfo | Install activity (Q2), file write (Q3) |
| 4 | App derives key → fromMnemonic()/fromHex() hooked by trackKeyDerivation() | In-process (not observable) | GAP — code review / SCA |
| 5 | Secret base64-encoded, smuggled in X-Request-Id to exfil host | DnsRequest / proxy | DNS to exfil host (Q1), header inspection (native) |
Consolidated IOC Table
Indicators trace to Socket and Datadog Security Labs (both 2026-07-09), corroborated by BleepingComputer and The Hacker News. The exfil host is a lookalike of legitimate Injective infrastructure — validate before global block.
| Type | Value | Conf | Action | Context |
|---|---|---|---|---|
| Package@ver | @injectivelabs/sdk-ts@1.20.21 | high | detect | Primary backdoored package (wallet-key stealer) |
| Exfil host | testnet.archival.chain.grpc-web.injective[.]network | high | detect | Secrets base64-smuggled in X-Request-Id header |
| Code marker | trackKeyDerivation() | high | hunt | Malicious hook function name |
| Code marker | hook on PrivateKey.fromMnemonic() / fromHex() | high | hunt | Key-derivation exfil hooks (marker "fm") |
| Clean version | @injectivelabs/sdk-ts@1.20.23 | high | enrich | Safe rollback target (also 1.20.20 and earlier) |
| SHA-256 | 103c4e6181151c1bcfedc41506cd1815458c38375d08a8fcd9981dbe0b965ce0 | high | detect | Malicious artifact |
| SHA-256 | 9a59eb454f3ca3fe91214136ee5edd417cc47a80e6f169b52099d6561944baf9 | high | detect | Malicious artifact |
The malicious 1.20.21 pin also affects these 17 scoped packages (full list in Section 10): utils, networks, ts-types, exceptions, wallet-base, wallet-core, wallet-cosmos, wallet-private-key, wallet-evm, wallet-trezor, wallet-cosmostation, wallet-ledger, wallet-wallet-connect, wallet-magic, wallet-strategy, wallet-turnkey, wallet-cosmos-strategy.
Affected Surface & Telemetry Matrix
| Surface | Required telemetry | Priority | Gap risk |
|---|---|---|---|
| Developer / build workstations | ProcessRollup2 (node/npm), FileCreateInfo (node_modules) | Critical | Medium — JS file writes are not always captured by EDR |
| CI/CD runners | Build logs, registry pulls, provenance attestations | Critical | High — runners often outside EDR coverage |
| Endpoint / egress network | DnsRequest, proxy HTTP logs (headers) | High | Medium — header inspection needs a TLS-terminating proxy |
| Package registry / SCA | Lockfiles, `npm ls`, SBOM, Socket/OSV feeds | Critical | Low — this is the authoritative surface |
| In-process key theft | — | — | GAP — the mnemonic hook runs inside Node; not endpoint-observable |
ATT&CK Mapping
| Tactic | Technique | Behavior | Query / Control |
|---|---|---|---|
| Initial Access | T1195.002 Supply Chain: Software Dependencies | Backdoored npm package | Q2, Q3, SCA sweep |
| Execution | T1059.007 JavaScript | trackKeyDerivation() hook runs in Node | GAP (native/code review) |
| Credential Access | T1552.001 Unsecured Credentials: Files | Steal BIP-39 mnemonics / private keys | Q1, rotate keys |
| Collection | T1005 Data from Local System | Capture key material at derivation | Native / SCA |
| Exfiltration | T1041 / T1071.001 Exfil over C2 (HTTP header) | base64 in X-Request-Id to exfil host | Q1, proxy header hunt |
| Defense Evasion | T1036.005 Masquerading: legit name/location | Signed via OIDC provenance; lookalike exfil host | Provenance verification |
Native Audit-Log Hunts
For a JS supply-chain compromise the registry/SCA surface is authoritative — run these first, they beat any endpoint query:
- Lockfile / SBOM sweep (highest fidelity): grep every repo, build image, and artifact cache for
1.20.21under any@injectivelabspackage inpackage-lock.json,yarn.lock,pnpm-lock.yaml, and SBOMs. Any hit = exposed. - Installed tree check: on dev/build hosts run
npm ls @injectivelabs/sdk-ts(and the 17 sibling packages) and confirm the resolved version is not1.20.21. - Registry mirror / proxy logs: query your internal npm proxy (Artifactory/Verdaccio/Nexus) for downloads of any
@injectivelabspackage version1.20.21since 2026-07-08. - Egress proxy header inspection: if a TLS-terminating proxy is in place, hunt outbound requests to
*.grpc-web.injective.networkcarrying an anomalousX-Request-Id(long base64) header. - CI/CD + VCS audit: review GitHub Actions OIDC trusted-publisher config and release history for the Injective SDK repo; verify npm provenance attestations on installed versions; look for release artifacts published in the compromise window.
- Secrets exposure: enumerate hosts/CI that handle wallet mnemonics or private keys and cross-reference against exposed repos — those keys are compromised.
CrowdStrike LogScale CQL Hunt Queries
This is a JavaScript supply-chain compromise: the key-theft hook runs inside Node and is not endpoint-observable. The queries below cover the network exfil and install/file artifacts; the authoritative detection is the SCA/lockfile sweep in Section 7. Coverage gaps are flagged in Section 15.
Looks for: any host resolving the exact exfil hostname the backdoor smuggles secrets to. FP: the host mimics real Injective archival infrastructure — a legitimate Injective app could plausibly resolve a sibling grpc-web.injective.network name, so confirm the exact FQDN and correlate with a wallet/DeFi workload before escalating.
// HUNT: DNS to the Injective SDK backdoor exfil host // MITRE: T1041 / T1071.001 | CONF: high FP: medium COST: low | REQUIRES: DnsRequest // TUNING: match the EXACT FQDN; allowlist known-legit Injective infra endpoints in use by sanctioned apps #event_simpleName=/DnsRequest/ | DomainName=/^testnet\.archival\.chain\.grpc-web\.injective\.network$/i | table([ComputerName, aid, DomainName, ContextBaseFileName], limit=200)
Looks for: a package-manager process whose command line references @injectivelabs — surfaces hosts that pulled the SDK so you can then check the resolved version. FP: legitimate installs of the SDK look identical; treat hits as a triage list, not a detection, and confirm the version via npm ls.
// HUNT: npm/yarn/pnpm install of @injectivelabs (triage list for version check) // MITRE: T1195.002 | CONF: medium FP: medium COST: low | REQUIRES: ProcessRollup2 // TUNING: after a hit, run `npm ls @injectivelabs/sdk-ts` on the host; only 1.20.21 is malicious #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^(node\.exe|npm\.cmd|npm|yarn|yarn\.cmd|pnpm|pnpm\.cmd)$/i | CommandLine=/@injectivelabs/i | table([ComputerName, aid, FileName, CommandLine, ParentBaseFileName], limit=200)
Looks for: writes of @injectivelabs package files into a node_modules tree — evidence the SDK (or a sibling wallet package) was installed on the endpoint. FP: fires for any install of these packages, benign or not; use it to build the exposure list, then confirm the version.
// HUNT: @injectivelabs package files landing in node_modules // MITRE: T1195.002 | CONF: medium FP: medium COST: low | REQUIRES: FileCreateInfo // TUNING: JS file-write telemetry is partial; pair with Section 7 lockfile sweep for authoritative coverage #event_simpleName=/FileCreateInfo|NewExecutableWritten/ | TargetFileName=/@injectivelabs\\(sdk-ts|wallet-|utils|networks|ts-types|exceptions)/i | table([ComputerName, aid, TargetFileName, ContextBaseFileName], limit=200)
Looks for: which node/electron processes talk to Injective gRPC infrastructure at all — a scoping query to inventory hosts that use the SDK, not a detection. FP: intentionally broad and matches legitimate Injective apps; use only to bound the population that needs the Section 7 version check.
// HUNT: exposure inventory — node/electron resolving Injective gRPC infra // MITRE: T1195.002 | CONF: low FP: high COST: low | REQUIRES: DnsRequest // TUNING: scoping-only; do NOT alert on this — feed hits into the lockfile/npm-ls sweep #event_simpleName=/DnsRequest/ | DomainName=/grpc-web\.injective\.network$/i | ContextBaseFileName=/^(node\.exe|electron\.exe)$/i | table([ComputerName, aid, DomainName, ContextBaseFileName], limit=200)
CrowdStrike Custom IOA Recommendations
IOA-1 · Beacon to the backdoor exfil host
Type: Network / DNS. Domain (exact): testnet.archival.chain.grpc-web.injective.network. Action: Detect, and block the FQDN at the egress proxy. Exclusion: none — no sanctioned workload should reach this exact host.
The remaining queries are hunt/triage or scoping and are not IOA-promotion candidates — a legitimate SDK install is indistinguishable from a malicious one at the endpoint layer, so the version decision belongs to SCA/registry tooling (Section 7), not a Custom IOA. Promote IOA-1 only.
Machine-Readable IOC Appendix
type,value,action,severity,expiration,description,tags domain,testnet.archival.chain.grpc-web.injective.network,detect,critical,2027-01-11,Injective SDK backdoor exfil host,campaign:InjectiveNpm sha256,103c4e6181151c1bcfedc41506cd1815458c38375d08a8fcd9981dbe0b965ce0,detect,high,2027-01-11,Injective SDK backdoor artifact,campaign:InjectiveNpm sha256,9a59eb454f3ca3fe91214136ee5edd417cc47a80e6f169b52099d6561944baf9,detect,high,2027-01-11,Injective SDK backdoor artifact,campaign:InjectiveNpm
@injectivelabs/sdk-ts@1.20.21 @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 # SAFE ROLLBACK: 1.20.23 (or 1.20.20 and earlier)
hook_fn = trackKeyDerivation()
hooked_targets = PrivateKey.fromMnemonic() (marker "fm")
PrivateKey.fromHex()
exfil_host = testnet.archival.chain.grpc-web.injective.network
exfil_channel = base64 payload in X-Request-Id HTTP header (POST/HTTPS)
window = 2026-07-08 20:59 - 21:48 UTC (~49 min, ~310 downloads)
delivery = GitHub OIDC trusted-publisher, trusted maintainer identity
# Find any @injectivelabs package pinned at the poisoned version
grep -rEl "@injectivelabs/[a-z-]+.{0,40}1\.20\.21" \
--include package-lock.json \
--include yarn.lock \
--include pnpm-lock.yaml .
# Any file printed = exposed repo -> remediate + rotate keys
Detection Validation Gates
| Gate | Check | Pass condition |
|---|---|---|
| Telemetry ready | DnsRequest + ProcessRollup2 flowing from dev/build hosts; egress proxy logging headers | Present in last 24h |
| Authoritative sweep | Lockfile/SBOM grep for @injectivelabs @1.20.21 across all repos + CI images | Zero hits (or all remediated) |
| Positive test | Add a benign DNS lookup of the exact exfil FQDN in a lab | Q1 fires within one interval |
| Exposure bound | Q4 inventory reconciled against SBOM | Every SDK-using host version-checked |
Hardening — Tiered & Deployable
- Rotate all exposed wallet keys / mnemonics (M1027-adjacent; ⚠ best-practice): any BIP-39 seed or private key handled on a host/CI that resolved
1.20.21is compromised — rotate and move funds. - Purge and roll back the package (M1051 Update Software): force
@injectivelabs/sdk-tsand all 17 siblings to1.20.23or an earlier clean version; delete1.20.21from caches, lockfiles, and CI images. - Block the exfil host (M1031 Network Intrusion Prevention): deny
testnet.archival.chain.grpc-web.injective.networkat the egress proxy/firewall and import the Section 10 CSV to Falcon.
- Deterministic, script-free installs in CI (M1016 Vulnerability Scanning; ⚠ best-practice, no formal CIS): use
npm ciagainst a committed lockfile and--ignore-scripts; fail builds on lockfile drift. - Continuous SCA (M1016): enable Socket / OSV / Dependabot on every repo and CI pipeline to catch malicious versions at PR time.
- Verify npm provenance attestations (M1054 Software Configuration): require and check provenance on installed packages; block unverified publishes.
- Internal package proxy with allowlist (M1016; Artifactory/Verdaccio/Nexus): mirror and vet third-party packages; freeze new versions for a soak window before promotion.
- SLSA build provenance + signed releases (M1054): enforce provenance-verified, reproducible builds across the pipeline.
- Isolate key material to hardware (M1027-adjacent; ⚠ best-practice): move signing to hardware wallets/HSMs so an SDK compromise cannot read a raw mnemonic in process memory.
Deployable Playbooks
Playbook A — Remediate the poisoned dependency (bash / npm)
# 1. Sweep every repo for the poisoned pin
grep -rEl "@injectivelabs/[a-z-]+.{0,40}1\.20\.21" --include '*lock*' .
# 2. Pin to a clean version and rebuild deterministically
npm pkg set dependencies.@injectivelabs/sdk-ts=1.20.23
rm -rf node_modules package-lock.json
npm install --ignore-scripts
npm ci
# 3. Purge the poisoned version from the local + CI cache
npm cache verify
npm cache clean --force
Playbook B — Block exfil host + confirm no beacons (bash)
# Proxy/firewall: deny the exact exfil FQDN # (example: add to blocklist ACL, then verify no host has resolved it) echo "testnet.archival.chain.grpc-web.injective.network" | tee -a /etc/proxy/blocklist.d/injective-npm.txt # Then run HuntPack Q1 in Falcon to confirm zero resolutions fleet-wide.
Playbook C — CI hardening (workflow policy)
# package.json - enforce clean, script-free installs npm pkg set scripts.preinstall="echo use npm ci" # CI step: install with a frozen lockfile and no lifecycle scripts npm ci --ignore-scripts # Enable SCA gate (example: Socket / OSV) as a required status check on PRs # Require npm provenance verification before promotion to the internal proxy
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Scope | Lockfile/SBOM sweep for @injectivelabs @1.20.21 across repos, CI, and dev hosts (Section 7) | AppSec | List of exposed repos/hosts |
| Contain | Block the exfil FQDN at egress; freeze affected pipelines; halt deploys built in the compromise window | SOC / Platform | Proxy deny entry, pipeline hold |
| Rotate | Treat every wallet key/mnemonic touched by an exposed app as compromised — rotate keys, move funds, revoke API/publish tokens | Crypto Ops / IAM | Key-rotation log |
| Eradicate | Roll all @injectivelabs packages to 1.20.23; purge 1.20.21 from caches/lockfiles; rebuild with npm ci --ignore-scripts | Dev teams | Clean lockfile diff |
| Recover | Re-enable pipelines with SCA + provenance gates; confirm Q1 returns zero beacons; audit GitHub OIDC/release config | Platform | Green SCA run, clean Q1 |
Detection Coverage Map
| Technique | Behavior | CQL | IOA / Native | Coverage |
|---|---|---|---|---|
| T1041 / T1071.001 | Exfil to backdoor host | Q1 | IOA-1 + proxy block | Good |
| T1195.002 | Poisoned package present (version) | Q2, Q3 | SCA / lockfile sweep | Partial (CQL) / Good (SCA) |
| T1059.007 | trackKeyDerivation() hook execution | — | Code review / SCA | Gap |
| T1552.001 | Mnemonic/private-key theft in process | — | Key rotation (assume compromise) | Gap |
| T1036.005 | OIDC provenance abuse | — | VCS/CI audit | Partial |
| — | SDK exposure inventory | Q4 | SBOM reconcile | Scoping |
Known gaps (honest): the actual key theft and the trackKeyDerivation() hook run inside Node and are not endpoint-observable — there is no CQL that proves theft occurred, so the response model is "assume compromise and rotate." Endpoint queries (Q2/Q3) only build an exposure list; the authoritative detection is the SCA/lockfile sweep in Section 7. Validation gates (Section 11) must pass before relying on this coverage.
Hunt Summary Ticket
TITLE: Injective Labs npm supply-chain — @injectivelabs/sdk-ts@1.20.21 wallet stealer
SEVERITY: Critical — crypto key/mnemonic theft via backdoored dependency
SCOPE: Dev/build workstations, CI runners, and repos using @injectivelabs packages
HYPOTHESIS: Poisoned 1.20.21 hooks fromMnemonic()/fromHex(), exfils secrets base64
in X-Request-Id header to testnet.archival.chain.grpc-web.injective.network.
QUERIES: Q1 exfil DNS · Q2 pkg-mgr install · Q3 node_modules write · Q4 exposure inv
DO FIRST: Lockfile/SBOM sweep for @injectivelabs @1.20.21 (Section 7); block exfil FQDN
FINDINGS: ____ exposed repos ____ hosts w/ 1.20.21 ____ exfil beacons
GAPS: In-process key theft = not endpoint-observable -> ROTATE KEYS (assume compromise)
ACTIONS: Roll to 1.20.23, purge caches, rotate wallet keys + publish tokens, audit OIDC
OWNER: __________ VERSION: v0.2 · 2026-07-11
Changelog
table() calls now carry an explicit row limit: the default is 200 and truncation is silent, so a capped result was indistinguishable from a complete one. Atomic-IOC sweeps (filters over 5+ hashes or C2 IPs) use limit=max so a wide infection is never silently under-scoped; behavioural hunts use limit=200, where exceeding the cap indicates the query needs tuning. Where present, event names that do not exist in the Falcon data model were corrected (e.g. ServiceInstalled is a Sysmon concept, not a Falcon event; ElfFileWritten is ELFFileWritten) — such queries could never return a row. No detection logic, fields, or IOCs changed.References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| 1 | Socket — Compromised Injective SDK npm Package (2026-07-09) | Package list, exfil endpoint, hooked functions, hashes | 2026-07-11 |
| 1 | Datadog Security Labs — sdk-ts Backdoor (2026-07-09) | X-Request-Id exfil, timeline, versions | 2026-07-11 |
| 2 | BleepingComputer — Injective SDK infected with wallet stealer (2026-07-09) | Downloads, repo compromise, deprecation status | 2026-07-11 |
| 2 | The Hacker News — Injective Labs GitHub Compromise (2026-07-09) | OIDC abuse, transitive packages | 2026-07-11 |
| 3 | MITRE ATT&CK | Technique IDs and mitigations (M-numbers) | 2026-07-11 |