Tech 9 min read

node-ipc 9.x/12.0.1 stealer on require(): 12.0.1 hash-gated, not postinstall

IkesanContents

TL;DR

Last updated May 14, 2026 UTC. Malicious versions: node-ipc@9.1.6, node-ipc@9.2.3, node-ipc@12.0.1. npm registry latest is back to 12.0.0.

What happened Three node-ipc versions shipped a credential stealer that fires on require("node-ipc") (CommonJS), not on postinstall. 9.x runs on every load; 12.0.1 is gated by a SHA-256 module-filename check, so it only fires on targeted machines.

What to do

  1. Pin 9.x users to 9.2.1, 12.x users to 12.0.0; regenerate the lockfile
  2. Treat all npm, GitHub, SSH, cloud, Kubernetes, Terraform, DB, and AI-tool secrets visible from affected machines as compromised
  3. Rotate those secrets before bringing the machines back online

What to grep node-ipc.cjs inflated to ~117KB, $TMPDIR/nt-*, __ntw=1 in process environment, sh.azurestaticprovider[.]net, 37.16.75.69, large *.bt.node.js DNS TXT queries.


Three node-ipc versions were published to npm on May 14, 2026 UTC, and Socket flagged them as malicious within minutes. The affected versions are 9.1.6, 9.2.3, and 12.0.1. The Hacker News first report was based on Socket’s and StepSecurity’s analyses, and both primary sources treat them as a credential stealer / backdoor aimed at developer and CI secrets.

The full attack chain looks like this.

flowchart TD
    A[npm install / lockfile resolve<br/>node-ipc 9.1.6 / 9.2.3 / 12.0.1] --> B[require node-ipc<br/>CommonJS entrypoint]
    B --> C[Obfuscated IIFE at end of node-ipc.cjs runs]
    C --> D{Version}
    D -->|9.1.6 / 9.2.3| E[No hash gate<br/>fork immediately]
    D -->|12.0.1| F[SHA-256 hash gate<br/>module filename match only]
    E --> G[Daemonize child process<br/>__ntw=1 / TMPDIR/nt-PID]
    F --> G
    G --> H[Host discovery<br/>SSH / GitHub / Kubernetes / Terraform / cloud / Keychain]
    H --> I[Redirect DNS resolver to C2 IP 37.16.75.69]
    I --> J[Send gzip chunks as *.bt.node.js TXT queries]

Looking at npm metadata right now, node-ipc’s latest is back to 12.0.0, and the three malicious versions no longer show up in the normal versions list. npm’s time metadata still records the publish times: 12.0.1 at 2026-05-14 14:25:30 UTC, 9.2.3 at 14:26:01 UTC, and 9.1.6 at 14:26:25 UTC. Any environment that pulled them into a lockfile or an internal registry mirror during that window needs to be combed through separately.

The trigger is not in postinstall

Walking through npm install scripts won’t reach the trigger. In StepSecurity’s and Socket’s analyses, the malicious code was appended to the end of node-ipc.cjs as an obfuscated IIFE. preinstall, install, and postinstall are not used.

When a CommonJS caller does require("node-ipc"), node-ipc.cjs is loaded and the malicious payload runs. Socket notes that the ESM-side node-ipc.js does not include the same appended code, but it’s enough for the app itself, tests, build tools, or any older dependency to touch the CommonJS entrypoint. An “install succeeded but the app hasn’t been started yet” environment behaves differently from a “CI ran the test suite” environment.

In the Mini Shai-Hulud campaign that spread into TanStack and Mistral, optional dependencies and install scripts were the visible artifacts. For node-ipc, narrowing the search to install logs alone misses environments where the payload has already executed.

9.x runs broadly, 12.0.1 is narrowed by a hash gate

Although the three versions share a name, they don’t share execution conditions. StepSecurity explains that only 12.0.1 carries a SHA-256 hash gate, and on environments whose module filename does not match, it stops short of full replacement. The 9.x versions have no such gate; on any environment that loads them, the payload runs.

Item9.1.6 / 9.2.312.0.1
Hash gateNoneYes (SHA-256 of module filename)
Behavior on requireForks immediately, daemonizesForks only when the gate matches; otherwise just appends an __ntRun export and exits
ReachEvery environment that loads the packageOnly targets the attacker pre-selected
Original node-ipc APIStill works normallyStill works normally
Roll back to9.2.112.0.0

The hash gate itself is straightforward: take the SHA-256 of the module filename passed to require, then compare it against a constant the attacker baked in. It only fires when node-ipc is loaded on a specific developer machine or CI runner; everywhere else it merely adds __ntRun and looks like nothing happened. Because of that, the fact that 12.0.1 was installed and “the app ran normally” doesn’t rule anything out — the environment may still have been the targeted one, and it stays on the watchlist.

This difference changes the priority of impact assessment. If 9.1.6 or 9.2.3 shows up in a lockfile, node_modules, CI cache, or internal mirror, treat “was this actually loaded” as the question to pursue aggressively. For 12.0.1, because the confirmed off-target behavior is to leave the API intact and just add an __ntRun export, environments where the app simply “kept working” still belong on the watchlist.

On execution, the payload forks a child process and uses __ntw=1 as the environment variable that splits the daemon-side code path. It also uses a working directory of the form $TMPDIR/nt-<PID>/. On a dev machine, check ps auxeww | grep __ntw; on a Linux CI runner, check __ntw in /proc/*/environ, plus nt-* directories under the temp dir.

The theft target is dev/CI credentials, broadly defined

The collection target is not just npm tokens. Socket’s analysis lists environment variables, SSH keys, GitHub CLI config, GitLab CLI, npm, Yarn, Netrc, Terraform, Kubernetes, Docker, Helm, Rancher, cloud configuration, DB configuration, shell history, macOS Keychain, Linux keyring, KWallet, FileZilla, and OpenVPN connection settings. StepSecurity adds Claude AI and Kiro IDE configuration on top, framing it as broad developer-secret coverage.

This sits close to the PCPJack Credential Stealer case where cloud credentials were stolen. The entry point is an npm package, but what’s stolen is the credentials a developer machine or runner routinely touches while working. If .env, cloud configuration, Kubernetes tokens, Terraform state, and DB passwords all lived in the same environment, removing node-ipc is followed by inventory and rotation of the credentials that were accessible from there.

DNS logs alone may not surface bt.node.js

On the network side, sh.azurestaticprovider[.]net is the most visible artifact. The name is shaped to look like Microsoft’s legitimate azurestaticapps.net, but the domain is unrelated. At the time of StepSecurity’s analysis it resolved to 37.16.75.69, which was being used as the C2 (the attacker’s command server).

The second layer is data exfiltration over DNS TXT. The payload first resolves the C2 domain via 1.1.1.1 or 8.8.8.8, then switches the resolver to point directly at the C2 IP. It then sends chunks of a gzip archive as TXT query labels. Socket points to high volumes of *.bt.node.js TXT queries with xh, xd, and xf prefixes as the detectable signal.

The reason for choosing DNS TXT is structural. Corporate egress firewalls log and gate HTTP and HTTPS, but UDP/53 is hard to block in ordinary operations and tends to be lightly monitored. If the attacker controls the authoritative server on the C2 side, the label portion of TXT queries becomes a one-way data channel by itself. Switching the resolver from 1.1.1.1 / 8.8.8.8 to 37.16.75.69 is what bypasses the corporate resolver and aims directly at the attacker-controlled authoritative server, which is how the traces disappear from passive DNS visibility on the organization side.

These bt.node.js queries don’t necessarily appear on public DNS. StepSecurity notes that because the C2 receives queries directly as the DNS sink, organizations relying solely on corporate resolver logs won’t see them. Instead, slice on UDP/53 from application processes to 37.16.75.69, outbound traffic to sh.azurestaticprovider.net or 37.16.75.69:443, and short-term spikes in TXT query volume.

Pulling the three versions out of lockfiles and caches

StepSecurity’s checks are direct. Search dependency trees and lockfiles for the three versions.

npm ls node-ipc
npm ls node-ipc --all 2>/dev/null | grep -E '9\.1\.6|9\.2\.3|12\.0\.1'

grep -E '"node-ipc".*"(9\.1\.6|9\.2\.3|12\.0\.1)"' package-lock.json
grep -E 'node-ipc@(9\.1\.6|9\.2\.3|12\.0\.1)' yarn.lock
grep -E 'node-ipc.*9\.1\.6|9\.2\.3|12\.0\.1' pnpm-lock.yaml

pnpm’s lockfile format can make grep noisy, so even when there’s no hit, eyeball the node-ipc entry directly. Tarballs may still live in CI caches, internal npm proxies, Verdaccio, Artifactory, or Docker image layers. Even if the public npm registry no longer surfaces them, internal caches run on a separate clock.

StepSecurity lists the known clean versions: 9.x users should roll back to 9.2.1, 12.x users to 12.0.0. On machines and runners that actually loaded one of the malicious versions, inventory and rotate the secrets that were visible in that environment before rolling the version back. The SHA-256 of the malicious node-ipc.cjs (96097e0612d9575cb133021017fb1a5c68a03b60f9f3d24ebdc0e628d9034144), the tarball hash, and the fact that node-ipc.cjs inflated to roughly 117KB are also useful indicators.

node-ipc had a 2022 incident too

node-ipc was already burned hard in 2022 by malicious code. At the time, the issue was protest-driven destructive code that overwrote files on systems located in Russia and Belarus, plus the peacenotwar dependency. Per Socket’s framing, the current three versions are closer to a suspicious re-publish of a known package or a re-introduction of malicious code than to typosquatting (the trick of standing up a fake package whose name is slightly off).

The npm account atiertant used this time is, per Socket, listed among node-ipc’s maintainers, but had no recent publish history under this package. StepSecurity also mentions a public theory that an expired domain was used to hijack the npm account-recovery email address. The intrusion path isn’t pinned down yet, but a popular package left under a dormant maintainer is itself an entry point for an attacker.

Recent npm damage continues in a shape distinct from both the bulk-spam style covered in the RubyGems signup pause post and the CI/OIDC-abuse style of Mini Shai-Hulud.

References