TL;DR
- StepSecurity's analysis (July 11) of
jscrambler@8.14.0: tarball jumps from 37.8 kB (8.13.0) to 7.9 MB, adds apreinstallhook runningdist/setup.js, which unpacks a Rust native binary fromdist/intro.js(custom container, not JavaScript) into a random dotfile under the OS temp directory and spawns it detached. - No matching commit, tag, or release for 8.14.0 in the public jscrambler GitHub repository per StepSecurity and SafeDep; latest tag remains 8.13.0. The version went straight to npm under the legitimate maintainer account.
- Static analysis points to a browser-credential and crypto-wallet stealer (Chrome/Firefox
Login Data, LevelDB for extension wallets, embedded BIP39 wordlist). The Linux binary imports libbpf and loads eBPF viabpf_object__open_memfrom an in-memory buffer. - Remediation: if you installed 8.14.0, treat the host as compromised; downgrade to 8.13.0, hunt orphaned temp-dir processes from install time, rotate every browser-session credential and CI secret reachable from affected machines. 8.14.0 was not unpublished; lockfiles can still resolve it.
What jscrambler is and why this hurts
jscrambler is the official npm CLI for Jscrambler's commercial JavaScript obfuscation and code-integrity service. It has a long version history, multiple @jscrambler.com maintainers, and real presence in CI pipelines that obfuscate production bundles before deploy. That makes a trojanized release a high-value supply-chain hit, not a obscure typosquat.
StepSecurity's OSS AI Package Analyst scored 8.14.0 at 0 (maximum suspicion) on publish the same day, triggering their manual teardown.
How the install hook works
Version 8.14.0's package.json adds a preinstall script absent from prior releases. dist/setup.js reads dist/intro.js, checks a 5-byte magic header (1B 43 53 49 01), and parses a custom container: per-platform ID byte, size fields, gzip-compressed native executables for Linux, Windows, and macOS. The loader picks the entry matching process.platform, decompresses to a randomly named dotfile in the system temp directory (.exe suffix on Windows), chmods executable, and spawns detached with unref() so the process survives after npm install exits.
Hiding compiled payloads inside a .js filename next to legitimate build artifacts like mutations.js keeps the loader script short; the malicious logic never appears as reviewable JavaScript source.
Payload capabilities beyond a typical npm stealer
StepSecurity's static analysis of the extracted binaries surfaced:
- Embedded SQLite strings matching Chrome/Firefox credential stores (
Login Data,Cookies,Web Data,key4.db). - Embedded LevelDB (Chromium
Local Storage/ IndexedDB, where MetaMask-style extension wallets persist vault data). - Embedded BIP39 English wordlist for parsing wallet seed phrases.
On Linux, the binary dynamically links libbpf.so.1 and imports bpf_object__open_mem, bpf_object__load, bpf_program__attach, and related symbols. bpf_object__open_mem loads eBPF bytecode from an in-memory buffer embedded in the binary, so the program never hits disk before kernel load. StepSecurity notes recovering what that eBPF program does requires extracting and disassembling the bytecode (ongoing work). The binary also imports the generic syscall function instead of named wrappers for sensitive calls, keeping ptrace / memfd_create out of the import table.
Windows imports include IsDebuggerPresent and GetExtendedTcpTable (enumerate TCP connections by PID, often used to fingerprint analysis environments or close browser processes before copying credential stores). macOS imports sysctl / sysctlbyname for P_TRACED debugger detection.
Harden-Runner network captures from StepSecurity customers show outbound calls to check.torproject.org, archive.torproject.org, and IPs 37.27.122.124 and 57.128.246.79.
Publish path anomaly
StepSecurity and SafeDep both report no commit, tag, or pull request for 8.14.0 in the public jscrambler GitHub repository; the latest tag remains 8.13.0 (published June 30). The malicious version appeared on npm under the legitimate jscrambler_ maintainer account without the project's normal git-tagged release flow. That pattern points to compromised npm credentials or a hijacked publish pipeline; which one is not established in the public write-up.
8.15.0 landed the same day from the same account without the preinstall hook or bundled binary, but 8.14.0 remains installable from the registry. Any lockfile or cache that pinned 8.14.0 can still pull the bad tarball.
If you installed 8.14.0
Assume compromise on any machine or CI runner that ran npm install resolving to jscrambler@8.14.0:
- Downgrade or pin clean:
npm install jscrambler@8.13.0and purge8.14.0from lockfiles and caches. - Hunt persistence: random dotfiles in
/tmp,%TEMP%, or$TMPDIRcreated around install time; orphaned child processes spawned duringnpm install; Windows Task Scheduler hidden tasks; macOS~/Library/LaunchAgentsplists you do not recognize. - Rotate credentials logged into browsers on affected hosts: cloud consoles, npm/GitHub tokens, SSO sessions, extension-based crypto wallets. Move assets from wallets whose extension storage may have been read to fresh seeds on clean hardware.
- CI pipelines: if
jscramblerran in a build workflow, rotate every secret injected into that job (npm tokens, cloud keys, code-signing material).
| IOC | Value |
|---|---|
| Malicious package | jscrambler@8.14.0 |
| Suspicious file | node_modules/jscrambler/dist/intro.js (~7.8 MB, magic 1B 43 53 49 01) |
| Linux payload SHA256 | fbbcf4d8f98168f78f5c0c47a9ae56d59ec8ac84a7c9ca6b797fedfb8d62d2bd |
| Windows payload SHA256 | b7ca95d1b23c8e67416a25cedf741de0917c2096bbc9d24649eea7853d054903 |
| macOS payload SHA256 | c8fd47d36bdf7c825378593ab82ed8c24d1dc52e26b507812393e24e1d5201fd |
Why this is not "just another npm incident"
The install-time execution pattern matches recent scope compromises (Mastra's easy-day-js caret trap, TanStack, and others). What stands out here is the official vendor CLI with a decade-long clean history, a git-less npm publish, a native container disguised as JavaScript, and kernel instrumentation capability on Linux from a package many teams treat as build hygiene.
If your registry policy still keys only on postinstall scripts or known malware package names, this release is a reminder that preinstall runs before your audit step finishes and that absence from git history is a signal worth blocking on, not a footnote.