jscrambler 8.14.0 through 8.20.0: IronWorm infostealer and a self-propagating npm worm

Five versions of jscrambler, a build-time JavaScript obfuscation tool with approximately 15,800 weekly downloads, were published on July 11 carrying a cross-platform Rust infostealer. JFrog has attributed the payload to IronWorm, a worm it documented a month earlier and ties to the Shai-Hulud lineage.
The malicious versions are 8.14.0, 8.16.0, 8.17.0, 8.18.0, and 8.20.0. Jscrambler has confirmed the cause: a compromised npm publishing credential. Version 8.22.0 is the confirmed clean release.
What makes this different#
Most supply chain compromises in this ledger return risk_state: none with supply_chain.compromised: true. No CVEs, clean vulnerability record, active malware. The two signals exist independently.
jscrambler returns differently:
curl "https://api.attestd.io/v1/check?product=jscrambler&version=8.14.0" \
-H "Authorization: Bearer $ATTESTD_API_KEY"
{
"product": "jscrambler",
"version": "8.14.0",
"supported": true,
"risk_state": "critical",
"risk_factors": ["supply_chain_compromised"],
"confidence": 0.95,
"cve_ids": [],
"supply_chain": {
"compromised": true,
"sources": ["agent", "osv"],
"malware_type": "malware",
"description": "Malicious code in jscrambler (npm)",
"advisory_url": "https://safedep.io/jscrambler-npm-supply-chain-compromise/",
"compromised_at": "2026-07-11T00:00:00Z",
"removed_at": null
},
"supply_chain_monitored": true
}
risk_state: critical is the supply chain signal elevating the overall verdict. cve_ids is empty. There is no CVE here and no vulnerability record. The critical state comes entirely from supply_chain_compromised in risk_factors. A system checking only CVE state would pass this version clean at confidence 0.95.
The clean upgrade target:
curl "https://api.attestd.io/v1/check?product=jscrambler&version=8.22.0" \
-H "Authorization: Bearer $ATTESTD_API_KEY"
{
"product": "jscrambler",
"version": "8.22.0",
"supported": true,
"risk_state": "none",
"risk_factors": [],
"supply_chain": {
"compromised": false,
"sources": []
},
"supply_chain_monitored": true
}
The five malicious versions#
The attacker pushed five malicious releases across approximately three hours on July 11. The delivery method changed partway through.
8.14.0, 8.16.0, and 8.17.0 run the malware from a preinstall hook. On clients where install scripts run automatically, the payload executes at npm install. npm 12 shipped on July 8, three days before this attack, with dependency install scripts off by default. On npm 12, the preinstall hook does not run without explicit approval. Older clients run it automatically.
8.18.0 and 8.20.0 moved the dropper into the package's main code and CLI. On these versions the payload fires when the package is imported or run, and npm install --ignore-scripts does not stop it.
Version 8.15.0, published between the first and second malicious releases, appears clean. 8.13.0, the release before the incident, is also clean. Both return risk_state: none with supply_chain.compromised: false.
All five malicious versions are deprecated but not removed. Any lockfile or command pinned to them still installs the stealer.
The payload#
The stealer is a Rust binary built for Linux, macOS, and Windows. It sweeps for cloud credentials from AWS, Azure, and Google Cloud including CI runner metadata endpoints, cryptocurrency wallet seeds and private keys from MetaMask, Phantom, and Exodus, browser passwords and cookies, Bitwarden and 1Password vaults, Discord, Slack, Telegram, and Steam sessions, and VPN configuration files.
It also targets AI coding tool configuration files: Claude Desktop, Cursor, Windsurf, VS Code, and Zed, where API keys and MCP server credentials tend to sit.
On Linux the payload links the kernel's BPF library and can load an eBPF program directly into the kernel from memory. On Windows and macOS it installs persistence: a hidden scheduled task relaunching every minute on Windows, a LaunchAgent reloading on login on macOS.
JFrog reports the worm uploads bulk stolen data to temp.sh over a direct connection, leaking the victim's IP. Its command-and-control channel runs through its own embedded Tor client.
The worm component#
IronWorm does not stop at theft. JFrog confirmed a self-propagation routine in the code. The payload hunts for npm tokens in environment variables and files including .npmrc, validates them against the npm registry, identifies packages with high download counts, injects a malicious preinstall script into their tarballs, and publishes the infected versions directly to the npm registry via raw HTTP PUT without calling the npm client.
JFrog says the worm confirmed this routine in analysis but did not confirm it successfully published through a stolen token in this campaign. The capability exists in the payload regardless.
jscrambler is installed as a development dependency or run from CI. Those environments hold exactly what the stealer and worm need: cloud keys, deploy tokens, and the npm credentials that would let the worm spread to other packages.
What to do#
Move to 8.22.0 and remove 8.14.0, 8.16.0, 8.17.0, 8.18.0, and 8.20.0 from lockfiles and package manager caches. If any of those versions ran on a machine, treat every secret it could reach as compromised. Rotate cloud keys, npm tokens, GitHub tokens, and AI tool API keys. Revoke browser, Bitwarden, Discord, Slack, and Steam sessions. Move cryptocurrency out of any wallet on that host.
Block the two confirmed C2 IPs: 37.27.122[.]124 and 57.128.246[.]79. Add temp.sh to egress monitoring. Check Task Scheduler on Windows and ~/Library/LaunchAgents on macOS for unfamiliar persistence entries.
Coverage#
jscrambler is covered. All five malicious versions return risk_state: critical and supply_chain.compromised: true. The clean releases (8.13.0, 8.15.0, 8.22.0) return risk_state: none with supply_chain.compromised: false. Responses above are live from the production API as of July 13, 2026.
API documentation: attestd.io/docs. Detection ledger: github.com/attestd-io/detection-ledger. Machine speed thesis: attestd.io/thesis.