CVE
Not assigned
CWE
CWE-506, CWE-494, CWE-829
Affected Surface
- `jscrambler` npm releases `8.14.0`, `8.16.0`, `8.17.0`, `8.18.0`, and `8.20.0`; clean baselines include `8.13.0`, `8.15.0`, and the remediated `8.22.0` line
- Developer workstations, CI runners, and release jobs that either installed the earlier preinstall-backed releases or later imported `jscrambler` programmatically or executed the CLI
- Hosts exposing browser sessions, cloud credentials, Bitwarden vault material, crypto-wallet extension storage, AI coding assistant configuration, or package publishing tokens to the Node.js process
The most important new package-registry incident from the last three days is not just that a trusted npm package was compromised. It is that the operator iterated during the same incident window from a classic install hook into an import-time execution path. On 11 July 2026, five malicious jscrambler releases appeared on npm: 8.14.0, 8.16.0, 8.17.0, 8.18.0, and 8.20.0. The early releases ran through preinstall; the later ones removed the hook and embedded the same loader in dist/index.js and dist/bin/jscrambler.js.
That distinction matters operationally. Many teams finally have decent controls around postinstall and preinstall, and npm 12’s recent defaults reinforce that direction. This incident shows why that is not enough. If your security model still treats “package import inside a trusted build tool” as harmless compared with lifecycle scripts, the later jscrambler releases are the exact counterexample.
Publicly confirmed affected versions
The publicly corroborated malicious set is:
jscrambler@8.14.0
jscrambler@8.16.0
jscrambler@8.17.0
jscrambler@8.18.0
jscrambler@8.20.0
The registry timeline is important because the bad versions were interleaved with clean ones rather than forming one simple contiguous vulnerable range:
2026-06-30 22:17:14Z 8.13.0 clean baseline
2026-07-11 15:12:40Z 8.14.0 malicious, preinstall-triggered
2026-07-11 17:07:14Z 8.15.0 clean interim release
2026-07-11 17:26:20Z 8.16.0 malicious, preinstall-triggered
2026-07-11 17:41:21Z 8.17.0 malicious, preinstall-triggered
2026-07-11 17:46:55Z 8.18.0 malicious, import/CLI-triggered
2026-07-11 17:53:25Z 8.20.0 malicious, import/CLI-triggered
2026-07-11 18:12:51Z 8.22.0 clean remediated release
That timeline means responders cannot rely on a loose semver rule such as “everything before 8.22 is bad” or “everything between 8.14 and 8.20 is bad.” They need exact version-aware scoping.
What changed in the package
The first compromised generation added two new files under dist/ and a new package script:
{
"scripts": {
"preinstall": "node dist/setup.js"
}
}
The new loader then treated dist/intro.js as a packed native payload container rather than JavaScript. Public analysis from multiple sources agrees on the high-level behavior:
- read
dist/intro.js - validate a custom magic header (
1b 43 53 49 01) - select the gzip-compressed payload matching
process.platform - decompress it into a random dotfile under the temp directory
- mark it executable
- spawn it detached and ignore errors
The execution shape can be reduced to:
const child = spawn(binaryPath, [], {
detached: true,
stdio: "ignore",
windowsHide: true
});
child.unref();
That is already enough to treat the host as compromised. A package whose documented purpose is JavaScript protection tooling has no benign reason to unpack a hidden native executable from a 7.8 MB blob and background it from the install tree.
The attacker pivoted away from preinstall
The part of the incident defenders should remember is not just the first version. It is the attacker adjustment after initial disclosure. The later malicious releases removed the lifecycle hook and moved the same loader into the normal runtime path:
// top-level IIFE added near the start of dist/index.js
(function () {
// read dist/intro.js
// unpack platform payload
// write temp executable
// spawn detached process
})();
This is a deliberate improvement from the attacker’s perspective:
- it survives
npm install --ignore-scripts - it evades scanners that only inspect lifecycle hooks
- it executes inside
require("jscrambler")or CLI use, which many teams still consider normal build behavior - it reaches the same developer or CI trust boundary as the original hook
That makes this incident a close cousin of the recent Rollup polyfill npm RAT chain and Injective SDK compromise: the meaningful exploit point is not always install time.
The payload is native, cross-platform, and aimed at developer machines
The most useful technical detail from the public reverse engineering is that the malware is not an obfuscated JavaScript stealer. The package ships a platform-switched native payload set:
Linux x86-64 ELF
Windows x86-64 PE32+
macOS arm64 Mach-O
The native payloads were reported as Rust-built and heavily focused on developer and operator environments. Publicly described targets include:
browser credential stores
browser cookies and local storage
Bitwarden vault material
MetaMask / Coinbase Wallet / Trust Wallet / Phantom extension data
AWS, GCP, and Azure credentials
Slack, Discord, and Telegram Desktop artifacts
Claude Desktop, Cursor, Windsurf, Factory, Zed, and VS Code MCP / AI config
This is why “it was only a dev dependency” is the wrong triage model. jscrambler is exactly the kind of package organizations install in build and release workflows, where the surrounding process already has:
source code
registry tokens
CI secrets
signing credentials
browser SSO sessions
cloud access
If the package executed on a developer workstation or runner, those adjacent secrets are part of the blast radius.
The malicious tarball is obvious at the registry level
Even without full reverse engineering, the package metadata itself gives strong structural indicators. Compared with 8.13.0, the malicious publish added:
- a new install-time or runtime loader
- a new 7.8 MB
dist/intro.jsblob - hidden platform-specific executable extraction behavior
Public package comparison also shows the tarball ballooning from a normal small CLI client into a multi-megabyte artifact. This is the kind of anomaly defenders can use before they know the full malware family name.
The import-time variants also force a second scoping question that many package incident reviews skip:
did the host only install the package?
or did the host actually import jscrambler / execute the CLI?
For 8.14.0, 8.16.0, and 8.17.0, install-time execution is enough. For 8.18.0 and 8.20.0, runtime usage matters as well.
Detection and scoping
Start with exact version hunting across manifests, lockfiles, package caches, and CI logs:
rg -n "jscrambler@(8\\.14\\.0|8\\.16\\.0|8\\.17\\.0|8\\.18\\.0|8\\.20\\.0)|\"jscrambler\"\\s*:\\s*\"(8\\.14\\.0|8\\.16\\.0|8\\.17\\.0|8\\.18\\.0|8\\.20\\.0)\"" \
package.json package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json .
Then look for the published file markers:
rg -n "dist/setup\\.js|dist/intro\\.js|1b43534901|windowsHide|unref\\(" \
node_modules .npm "$HOME"
Because the payload is detached into temp storage, host forensics should also search likely execution artifacts:
rg -n "check\\.torproject\\.org|archive\\.torproject\\.org|37\\.27\\.122\\.124|57\\.128\\.246\\.79" \
"$HOME" /tmp /var/tmp /var/log .
For the later malicious releases, answer the runtime question directly:
rg -n "require\\([\"']jscrambler[\"']\\)|from [\"']jscrambler[\"']|jscrambler " \
src scripts build .github
That separation between “resolved in dependency graph” and “executed in process” is what determines whether you are looking at exposure to a bad artifact or probable host compromise.
Response guidance
If any affected version was installed or executed:
- upgrade or pin to
8.22.0or another verified clean release - treat affected developer machines and CI runners as compromised hosts, not merely dirty dependency trees
- rotate npm, GitHub, cloud, CI, SSH, browser-session, and wallet-related secrets from a clean machine
- invalidate cached tarballs, base images, and runner layers that may continue to restore the malicious release
- review whether later build or deployment outputs were produced from the same compromised host
The key lesson from the jscrambler incident is not just that a legitimate npm package was hijacked. It is that the attacker adapted in real time from install-time execution to import-time execution, which is exactly where many organizations still have weaker guardrails. For AppSec and platform teams, that makes this one of the clearest recent examples of why trusted build dependencies are a runtime boundary, not just a package-resolution boundary.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.
References
- Socket: jscrambler npm Package Compromised in Supply Chain Attack
- Jscrambler security advisory: Unauthorized Publication of a Malicious npm Package affecting CI
- StepSecurity: jscrambler npm package publishes malicious preinstall binary
- GitHub issue: jscrambler v8.14.0 compromised?
- GitHub Advisory: GHSA-v3g3-4x77-rw68