OAK — OnChain Attack Knowledge

Worked example · 2026-07

Injective Labs SDK — a commit from a trusted maintainer account rides the OIDC auto-publish pipeline into 18 npm packages that exfiltrate seed phrases at key-derivation time — Injective Labs / npm (chain-agnostic) — 2026-07-08

Loss
No confirmed fund loss. The malicious @injectivelabs/sdk-ts@1.20.21 and 17 sibling packages were live on npm for roughly 50 minutes and the tainted version was downloaded 310 times against the package's ~50,000 weekly downloads. Injective reverted the payload 16 minutes after publication and shipped a clean 1.20.23 about 30 minutes later, and has stated there was no user impact. OAK records this as a near-miss with an unbounded tail: any private key or mnemonic that passed through the SDK during the window must be treated as exposed, exfiltration is silent by design, and there is no way for a downstream developer to know from the outside whether their keys were among the 310.
OAK Techniques observed
OAK-T15.002 (Supply-Chain / Vendor-Pipeline Compromise — primary, confirmed mechanism, in its package-registry sub-shape. The attacker had write access to Injective's GitHub repository and pushed a commit that added a payload file; the repository's OIDC trusted-publisher CI/CD pipeline then did what it was built to do and published the tainted build to npm automatically. No npm token was stolen — the registry credential was never the surface. See techniques/T15.002-supply-chain-vendor-pipeline-compromise.md). OAK-T11.009 (Trader-Tooling Supply-Chain Compromise targeting Developer Key Material — the payload's target cohort and substrate. It did not fire on install; it hooked PrivateKey.fromMnemonic() and PrivateKey.fromHex(), the SDK functions wallet builders, trading bots, DEX frontends, and payment tools call to derive or import keys, and captured the mnemonic and private key as they passed through. The victim is the developer environment and the applications built on it, not an end-user wallet UI. See techniques/T11.009-trader-tooling-supply-chain-env-key-compromise.md).
Attribution
unattributed. All malicious commits — and the revert — were authored under a legitimate maintainer account with an established history of contributions to the repository. Public forensic write-ups name the account; OAK does not reproduce the maintainer's personal identifiers here, because the account holder is a probable victim of credential compromise rather than a suspect, and the identifier adds nothing to the defensive lesson. Whether the account was credential-compromised, session-hijacked, or misused is not publicly established, and no group attribution has been offered. The tradecraft — runtime-obfuscated exfiltration hostname, header-channel data smuggling, silent failure handling, and a payload scoped precisely to key-derivation functions — is consistent with the broader 2024–2026 npm crypto-supply-chain campaign space (which overlaps DPRK-attributed BeaverTail / InvisibleFerret activity at the infrastructure-fingerprint layer), but OAK asserts no link on the evidence available.
Key teaching point
Provenance-based trust — "this commit came from a known maintainer, so the pipeline may publish it" — collapses the moment a maintainer account is compromised, and an OIDC trusted-publisher pipeline turns that collapse into an automatic, global release in about five minutes. Injective's supply chain did not fail because a credential leaked to a registry; it failed because the repository was the trust root and the pipeline was built to act on repository state without a human in the loop. The mechanism that made this fast and clean — no npm token to steal, no manual publish step, cryptographic attestation that the artefact genuinely came from Injective's repository — is the same mechanism that made the malicious build look more trustworthy than a hand-published one. OIDC trusted publishing is a real improvement over long-lived npm tokens and the answer is not to abandon it; the answer is that automatic publication must be gated on something the account holder alone cannot satisfy — a second maintainer's review on any commit touching key-handling paths, a publish delay with diff review, or protected paths that require signed approval. The compensating control on the consumer side is dependency pinning with review of the actual diff before version bumps, because a package that silently upgrades inherits every one of its publisher's bad days.

Summary

@injectivelabs/sdk-ts is Injective Labs' TypeScript SDK, drawing about 50,000 weekly downloads and used to build wallets, trading bots, decentralised exchanges, DeFi applications, and payment tools on Injective. Its GitHub repository publishes to npm through an OIDC trusted-publisher pipeline: a version bump on the repository triggers an automatic, credential-less publish whose provenance is cryptographically attested back to the repository.

On 2026-07-08 at 20:24 UTC, a commit authored under a maintainer account with an established contribution history added src/utils/key-derivation-telemetry.ts — 79 lines presenting themselves as telemetry. A version bump at 20:54 triggered the pipeline, and between 20:59 and 21:00 all 18 @injectivelabs-scoped packages were published at v1.20.21: sdk-ts itself plus networks, wallet-base, wallet-core, wallet-strategy, wallet-cosmos, wallet-cosmostation, wallet-evm, wallet-ledger, wallet-trezor, wallet-magic, wallet-private-key, wallet-turnkey, wallet-wallet-connect, and four others carrying transitive risk.

The payload did not run on install. It hooked PrivateKey.fromMnemonic() and PrivateKey.fromHex() — the functions a developer calls to derive or import a key — so it fired only when real key material passed through, which is what let it sit inside a legitimate-looking telemetry module. On trigger it captured the full mnemonic and private key, base64-encoded them, and batched them in memory on 2-second windows. Exfiltration went out as a POST with Content-Type: application/grpc-web+proto and an empty body, with the secrets carried in the X-Request-Id header — traffic shaped to look like ordinary gRPC-Web chatter. The destination hostname (testnet.archival.chain.grpc-web.injective.network, mimicking Injective's real gRPC-Web infrastructure) never appeared as plaintext in source: it was stored as a JavaScript character-code array and reconstructed at runtime. Errors were swallowed silently so nothing surfaced to the developer.

A revert commit removed the payload at 21:16, and a clean v1.20.23 was published at 21:47–21:49 with v1.20.21 deprecated on the registry. Total exposure window was under an hour; the tainted version recorded 310 downloads. Injective reported the compromise resolved with no user impact. Anyone who installed v1.20.21 in the window should treat every key and mnemonic that touched the package as compromised, rotate them, and audit transitive dependencies.

Timeline (UTC)

When Event OAK ref
(standing) Repository publishes to npm via an OIDC trusted-publisher pipeline: a version bump auto-publishes with attested provenance and no npm token in the loop (standing T15.002 surface)
2026-07-08 20:24 Commit authored under a trusted maintainer account adds src/utils/key-derivation-telemetry.ts (79 lines) T15.002 injection
2026-07-08 20:54 Version bump triggers the CI/CD publish pipeline T15.002 propagation
2026-07-08 20:59–21:00 All 18 @injectivelabs packages publish at v1.20.21, provenance-attested to Injective's repository T15.002 publication
2026-07-08 (window) Payload fires only on PrivateKey.fromMnemonic() / PrivateKey.fromHex(); captures mnemonic + private key, base64, 2-second in-memory batching; exfiltrates via empty-body POST with secrets in the X-Request-Id header to a runtime-reconstructed lookalike hostname T11.009
2026-07-08 21:16 Revert commit removes the payload (containment)
2026-07-08 21:47–21:49 Clean v1.20.23 published; v1.20.21 deprecated on npm (remediation)
post-event 310 recorded downloads of v1.20.21 against ~50,000 weekly; Injective reports resolution within the hour and no user impact (operator response)

What defenders observed

  • Pre-event (the repository was the trust root, and one account could move it). The pipeline's security model was "commits in this repository are trustworthy, therefore builds from this repository may publish automatically". A single compromised maintainer account satisfied that model completely. The control is to require something an individual account holder cannot unilaterally provide on the paths that matter: mandatory second-maintainer review for commits touching key-handling code, protected paths with signed approval, or a publish delay that puts a human diff review between merge and registry. Five minutes elapsed between the version bump and 18 published packages (M40).
  • Pre-event (auto-publish multiplied one commit into 18 packages). Fan-out is a design property here, not an accident: the packages publish together, so the blast radius of any single injection is the whole scope, including the wallet-ledger and wallet-trezor packages whose users had specifically chosen hardware isolation. Release fan-out should be an explicit part of a project's threat model — the question is how many artefacts a single merge can ship, and whether that number is acceptable without review.
  • At-event (the payload triggered on key derivation, not on install). Install-time scanning — the assumption behind much npm supply-chain tooling — would have found nothing. The malicious code only executed when PrivateKey.fromMnemonic() or PrivateKey.fromHex() was called with real key material. Detection has to look at what a package does with secrets it is handed, not only at what it does at install (T11.009).
  • At-event (exfiltration shaped to look like the SDK's own traffic). Secrets travelled in an X-Request-Id header on an empty-body POST typed application/grpc-web+proto, to a hostname that mimicked Injective's real gRPC-Web infrastructure and was reconstructed at runtime from a character-code array so it never appeared in source. Every choice here defeats a specific control: header-channel smuggling defeats body inspection, the lookalike hostname defeats domain-reputation checks, runtime reconstruction defeats static string scanning, and silent error handling defeats developer-visible failure. Egress allowlisting from build and bot environments is the control that survives all of them.
  • Detection and response (fast, and largely luck-independent of the consumer). The 50-minute window and the revert-then-clean-release sequence bounded this well, and Injective's response is the reason the incident reads as a near-miss. But the 310 downloads are not recoverable: exfiltration was silent, so no affected developer received a signal. Rotate-on-disclosure is the only sound posture for anyone who installed v1.20.21 — treat the keys as gone regardless of whether a breach is visible (M22).
  • Consumer-side (a floating dependency inherits its publisher's worst hour). Downstream projects that resolve @injectivelabs/* loosely would have picked up v1.20.21 automatically during the window. Pinning with a reviewed diff on version bumps is what converts a publisher compromise from an automatic exposure into a decision the consumer gets to make (M40).

Public references

Discussion

Injective belongs in OAK's T15.002 package-registry family alongside Ledger Connect Kit (2023-12), Solana web3.js (2024-12), and the Polymarket trader-tooling compromise (2026-01), and what it contributes to the class is the trusted-publisher inversion. The prior anchors are token-theft stories: someone stole a publish credential and used it. OIDC trusted publishing was designed to kill exactly that vector, and it did — there was no npm token here to steal. But it relocated the trust root to the repository, and the repository is guarded by maintainer accounts. The compromise therefore rode a pipeline that was, by design, credential-less, automatic, and provenance-attested. The malicious v1.20.21 carried a cryptographic attestation that it genuinely came from Injective's repository, and that attestation was true. Provenance answered the question it was built to answer and the answer was correct; the question was simply not the one that would have saved anyone.

This is worth stating carefully because the wrong lesson is available and attractive. Trusted publishing is better than long-lived npm tokens and projects should keep using it. The point is that provenance attests origin, not intent, and a supply chain whose only gate is origin will publish whatever a compromised maintainer account produces, at machine speed. The window from version bump to 18 live packages was about five minutes. The gate that closes this sits earlier — at the commit, on the paths that touch key material — and it must be a gate a single account cannot pass alone.

The payload's craft is the other durable contribution, and it is what makes the T11.009 mapping load-bearing rather than decorative. This was not a smash-and-grab postinstall script. It fired only when PrivateKey.fromMnemonic() or PrivateKey.fromHex() was handed real key material, which is both precise targeting and effective evasion: install-time scanners see an inert telemetry module, and the code is quiet on every machine that builds without touching a key. The exfiltration channel was chosen the same way — secrets in a header on an empty-body POST typed as gRPC-Web, to a hostname assembled at runtime that impersonated the SDK's own infrastructure, with errors swallowed. An analyst reading source would find no suspicious string; a network monitor without egress allowlisting would see the SDK talking to something that looked like Injective. Contributors documenting future registry compromises should record what triggers the payload as carefully as they record what it steals, because trigger design is where this cohort has been improving and it determines which detection layer has any chance of firing.

Finally, the 310 downloads are the reason this is filed as a near-miss rather than a non-event. Injective's response was genuinely fast and the outcome was probably close to the stated zero impact. But silent exfiltration means the absence of reported losses is not evidence of their absence, and the affected population — developers who happened to derive a key during a 50-minute window — has no way to self-identify. Rotate-on-disclosure exists precisely for this shape of uncertainty.

Techniques demonstrated (2)