
Research
/Security News
npm Package Uses Prompt Injection and Token Flooding to Disrupt AI Malware Scanners
A new npm package tests AI malware scanners with prompt injection, safety-triggering comments, context flooding, and obfuscated JavaScript.
Socket detected malicious node-ipc versions with obfuscated stealer/backdoor behavior in a developing npm supply chain attack.

May 14, 2026
9 min read


Socket’s threat feed has detected malicious activity in newly published versions of node-ipc, a long-running npm package previously associated with one of the most widely discussed supply chain incidents in the JavaScript ecosystem.
The affected versions confirmed as malicious are:
Socket’s AI scanner detected the newly published malicious versions within roughly three minutes of publication, classifying the activity as malware. Early analysis indicates that node-ipc@9.1.6, node-ipc@9.2.3, and node-ipc@12.0.1 contain obfuscated stealer/backdoor behavior. The malware appears to fingerprint the host environment, enumerate and read local files, compress and chunk collected data, wrap the payload in a cryptographic envelope, and attempt exfiltration through a network endpoint selected via DNS/address logic.
Socket’s incident response scan also noted historical malicious versions tied to the original 2022 node-ipc compromise. Versions 10.1.1 and 10.1.2 were associated with geo-targeted destructive malware that checked whether a system was located in Russia or Belarus before recursively overwriting files. Versions 11.0.0 and 11.1.0 included the peacenotwar dependency, which was previously linked to unauthorized file-writing behavior.
The latest incident appears to involve a suspicious republishing or reintroduction of malicious code into versions of a known package, rather than a typosquatting attempt. Socket classified all seven reviewed versions as malicious and recommends blocking them.
This is a developing story. Socket’s Threat Research team is continuing to analyze the package contents, confirm the full scope of the compromise, and extract indicators of compromise. Developers should avoid installing the affected versions and audit any recent installs of node-ipc, especially versions 9.1.6, 9.2.3, and 12.0.1.

The updated list of packages in this ongoing supply chain attack can be viewed at https://socket.dev/supply-chain-attacks/node-ipc
node-ipc is an npm package for inter-process communication in Node.js applications. The compromised package metadata routes CommonJS and ESM consumers to different entrypoints:
{
"type": "module",
"main": "node-ipc.cjs",
"module": "node-ipc.js",
"exports": {
"import": "./node-ipc.js",
"require": "./node-ipc.cjs"
}
}The malicious code is only present in node-ipc.cjs. It is appended as a single obfuscated IIFE at the end of the bundled CommonJS file. The clean ESM wrapper, node-ipc.js, imports the package implementation from local source files and does not contain the appended payload.
The package tarballs also carry a forensic artifact: every file in the reviewed tarballs is timestamped Oct 26 1985. That timestamp appears across the analyzed version artifacts and should be treated as a package-level indicator when investigating cached tarballs or registry mirrors.
node-ipc has twelve listed npm maintainers. One of them, atiertant, has publish rights but has been dormant on this package for years; recent releases have been published by other maintainers. This account was used to publish the three malicious releases.
Independent researcher Ian Ahl (@TekDefense, CTO at Permiso) was first to publicly identify the likely vector: takeover of a dormant maintainer account via an expired email domain:
Assuming the npm account recovery email for atiertant was indeed hosted on atlantis-software[.]net, the new domain owner was then able to trigger a standard npm password reset, receive the reset email at a mailbox under their control, and gain publish rights without ever compromising any of the maintainer's own infrastructure.
When require("node-ipc") resolves to node-ipc.cjs, the appended IIFE executes during module load. The payload does not use a postinstall script. It relies on runtime loading of the CommonJS bundle.
The payload exposes its runner function as __ntRun on the module exports in the normal path:
// Analyst note: simplified from the decoded payload.
runner.__ntRun = runner;
if (module && module.exports) {
if (hashGateMatched) {
module.exports = runner;
} else {
module.exports.__ntRun = runner;
}
}The __ntRun export is not just metadata. Any code that calls require("node-ipc").__ntRun() can trigger the runner again in that process. That creates an additional activation path through tests, build scripts, or other packages that inspect and call exported properties. Because the runner normally forks a detached child with __ntw=1, a downstream package that calls this export can cause a new collection and exfiltration attempt outside the original module-load event. The primary activation still happens automatically through setImmediate() after the CommonJS module loads.
The payload uses an environment variable named __ntw to distinguish the child execution path:
// Analyst note: simplified from the decoded payload.
if (process.env.__ntw === "1") {
return collectAndExfiltrate();
}
const childEnv = Object.assign({}, process.env, { __ntw: "1" });
delete childEnv.NODE_OPTIONS;
const child = child_process.fork(
path.resolve(module.filename),
[],
{
cwd: process.cwd(),
detached: true,
stdio: "ignore",
env: childEnv,
execArgv: [],
windowsHide: true
}
);
if (child.channel) child.channel.unref();
child.unref();If the fork succeeds, the parent process returns and the credential collection runs in the detached child. If the fork fails, or if __ntw=1 is already present, the payload runs collection in the current process.
The payload computes a special hash gate from the basename of the current module filename:
// Analyst note: simplified from the decoded payload.
const actual = sha256(path.basename(module.filename).toLowerCase()).toString("hex");
const expected = "bf9d8c0c3ed3ceaa831a13de27f1b1c7c7b7f01d2db4103bfdba4191940b0301";
const hashGateMatched = actual === expected;The reviewed artifacts confirm credential and configuration theft from developer machines and CI-like environments that load the CommonJS entrypointFor the analyzed node-ipc.cjs file, the basename hash does not match the expected value, so the package API is augmented with __ntRun and the rest of the library remains usable. If the gate matches in another filename context, the code replaces module.exports with the runner function. The filename preimage for the expected hash was not recovered from the local samples, so this should be described as a confirmed behavior branch with an unknown target filename.
The payload builds a host fingerprint using Node.js OS APIs:
// Analyst note: reconstructed from decoded calls.
[
os.platform(),
os.release(),
os.arch(),
os.hostname(),
os.endianness()
].join(" ");It also runs uname -a with a timeout and falls back to the Node.js fingerprint if that command fails. The archive always includes:
uname.txt, containing the command output or fallback fingerprintetc/hosts, if /etc/hosts can be readenvs.txt, containing sorted process.env entries as KEY=valueThe environment collection is not selective. Any token, cloud credential, CI secret, registry token, or database secret present in the process environment is written into envs.txt.
The payload chooses between separate decoded target lists for macOS and Linux/default platforms. The lists are not identical. In the analyzed payload, the macOS list contains 113 patterns and the Linux/default list contains 127 patterns. This count comes from the single identical malicious CommonJS file shared by all three reviewed package artifacts, not from a version-specific payload difference.
The collection logic supports home-directory expansion, relative paths from process.cwd(), single-segment globs, one-level dir/*/name patterns, and recursive **/name patterns from the current working directory. Recursive walking skips directories named node_modules and .git. Files larger than 4 MiB are skipped.
The targets focus on developer and infrastructure secrets, including:
tfvars files.env, .env.local, .env.production, database configuration files, shell histories, and database CLI historiesBrowser credential theft should be described precisely. The payload does not directly target Chrome Login Data, Chrome cookies, Chrome history, Safari history, or Safari cookies in the analyzed lists. It does target macOS Keychain databases, which can contain system and browser credentials, and it targets Firefox key*.db files on macOS, but it does not collect Firefox logins.json or cookies.sqlite in the decoded target list.
Collected files are renamed before archiving:
<host>/fixtures/f_<sha256(abs_path).slice(0,16)>_<sanitized_basename>The archive also includes:
<host>/fixtures/_paths.txtThat file maps the harvested archive entries back to their original absolute paths, giving the threat actor context for each collected credential.
The payload builds a POSIX ustar archive in memory, writes file headers and padded contents, then gzip-compresses the full tar buffer.
It does not keep the archive only in memory. The child writes a gzip archive to a temporary directory before exfiltration:
<os.tmpdir()>/nt-<pid>/<machineHex>.tar.gzThe payload attempts to delete this file with unlinkSync() after the exfiltration attempt finishes. If the process is interrupted, crashes, or is contained during execution, responders may find the temporary archive on disk.
The payload does not use http, https, or tls.connect() for exfiltration. It uses DNS TXT queries.
The decoded bootstrap resolver is:
sh[.]azurestaticprovider[.]net:443The domain sh.azurestaticprovider.net is a deliberate lookalike of Microsoft's legitimate Azure Static Web Apps domain (azurestaticapps.net). The substitution of apps → provider and the use of the sh subdomain is designed to pass a casual inspection of firewall or proxy logs as legitimate Azure traffic.
If the bootstrap host is not already an IP address, the payload tries to resolve it using 1.1.1.1 first and 8.8.8.8 second. At the time of publication, this IP was 37.16[.]75.69. It then uses the resolved address plus port 443 as the DNS server for TXT exfiltration. If bootstrap resolution fails, it falls back to the raw bootstrap string and still proceeds to build and write the archive before the DNS exfiltration attempt fails.
The exfiltration zone is:
bt[.]node[.]jsThe query labels use three record types:
xh.<machineHex>.<transferId>.<headerSig>.<chunkIndex>.<hexHeaderChunk>.bt[.]node[.]js
xd.<machineHex>.<transferId>.<bodySig>.<chunkIndex>.<hexBodyChunk>.bt[.]node[.]js
xf.<machineHex>.<transferId>.<footerSig>.0.<hexFooterJson>.bt[.]node[.]jsThe transferId is generated from five random bytes and encoded as 10 hex characters for each exfiltration session.
The header JSON includes:
{
"v": 1,
"machineHex": "<machine-id>",
"cloud": "none",
"archivePath": "<temp-archive-path>",
"gzipBytes": 0,
"hdrChunks": 0,
"datChunks": 0,
"hostLabel": "<sanitized-hostname>"
}
The cloud value is hardcoded to none; no cloud metadata service probing was observed.
The footer JSON is:
{
"done": true,
"dataChunks": 0
}DNS chunking is fixed:
Promise.all() windowFor a 500 KiB gzip archive, the expected data query volume is roughly 29,400 TXT queries, plus header and footer queries. That query count varies with the final compressed archive size. A burst of high-volume TXT queries matching xh, xd, and xf label prefixes under bt[.]node[.]js is a strong detection opportunity.
The decoded authentication key is:
qZ8pL3vNxR9wKmTyHbVcFgDsJaEoUiThe body encoding chain is:
gzip archive
to base64 text
convert base64 text to bytes
XOR with SHA-256 keystream
base64 encode the XOR output
substitute characters using a key-derived alphabet permutation
split into 31-character body chunks
hex encode each chunk for DNS label transportThe dynamic run confirmed this path by reassembling intercepted xd chunks, reversing the DNS-label hex transport, reversing the substituted base64 body encoding, and reproducing the captured gzip archive byte-for-byte.
Signatures use HMAC-SHA256 and are truncated to 12 hex characters:
bodySig = HMAC-SHA256(key + "|t", encodedBody).hex().slice(0, 12)
headerSig = HMAC-SHA256(key + "|p", headerJson).hex().slice(0, 12)
footerSig = HMAC-SHA256(key + "|q", machineHex + "|" + transferId + "|" + dataChunks).hex().slice(0, 12)The payload awaits resolveTxt() calls, but it does not parse returned TXT records or dispatch commands. No download-and-execute stage, command loop, or response-driven C2 behavior was observed in the decoded payload.
A controlled dynamic run forced the child path by setting __ntw=1 and instrumented filesystem, process, and DNS APIs. The run observed:
nt-<pid> directoryunlinkSync() cleanup of that archivesh[.]azurestaticprovider[.]netxh prefixxd prefixxf prefixReassembling and decoding the intercepted xd DNS chunks reproduced the captured .tar.gz byte-for-byte, confirming that the DNS body chunks carry the gzip archive.
The reviewed artifacts confirm credential and configuration theft from developer machines and CI-like environments that load the CommonJS entrypoint. Confirmed affected local artifacts include:
node-ipc 9.1.6 tarballnode-ipc 9.2.3 tarballnode-ipc 12.0.1 tarballThe malicious CommonJS file hash is identical across the reviewed unpacked package versions. CommonJS consumers using require("node-ipc") trigger the payload. ESM-only consumers using the package import export path do not load the malicious file in the reviewed package metadata, unless another dependency or direct require path loads node-ipc.cjs.
The payload does not establish persistence in the decoded sample. There is no observed cron, launchd, rc.d, service installation, or second-stage download. The operational impact is concentrated in the execution window: collection, archive creation, DNS TXT exfiltration, and attempted cleanup.
The decoded network indicators are:
sh[.]azurestaticprovider[.]net:443bt[.]node[.]jsxh, xd, xfThe samples do not provide enough evidence to attribute the operation to a named threat actor.
node-ipc versions and reinstall from a known-clean version.node-ipc.cjs hash.<tmp>/nt-<pid>/<machineHex>.tar.gz.node-ipc through CommonJS, ESM, or a transitive dependency.sh[.]azurestaticprovider[.]net:443.xh, xd, or xf under bt[.]node[.]js.node-ipc.cjs SHA-256 hash.nt-<pid> during live response, because interrupted runs may leave the gzip archive on disk.node-ipc.cjs SHA-256: 96097e0612d9575cb133021017fb1a5c68a03b60f9f3d24ebdc0e628d9034144node-ipc-9.1.6.tgz SHA-256: 449e4265979b5fdb2d3446c021af437e815debd66de7da2fe54f1ad93cbcc75enode-ipc-9.2.3.tgz SHA-256: c2f4dc64aec4631540a568e88932b61daebbfb7e8281b812fa01b7215f9be9eanode-ipc-12.0.1.tar.gz SHA-256: 78a82d93b4f580835f5823b85a3d9ee1f03a15ee6f0e01b4eac86252a7002981sh[.]azurestaticprovider[.]net37.16[.]75.69bt[.]node[.]jsxh.xd.xf.__ntw=1__ntRun<tmp>/nt-<pid>/<machineHex>.tar.gzuname.txt, envs.txt, etc/hosts, fixtures/_paths.txtqZ8pL3vNxR9wKmTyHbVcFgDsJaEoUi
Subscribe to our newsletter
Get notified when we publish new security blog posts!

Research
/Security News
A new npm package tests AI malware scanners with prompt injection, safety-triggering comments, context flooding, and obfuscated JavaScript.

Research
/Security News
The trojanized extensions use TinyGo-compiled WebAssembly and Solana transaction memos to resolve command-and-control infrastructure.

Security News
Anthropic says the directive cited national security concerns over a narrow jailbreak, but offered no specific technical details.