Bastava un “npm install” per essere compromessi. L’11 luglio 2026 la versione 8.14.0 del pacchetto jscrambler, tool di offuscamento JavaScript usato in migliaia di pipeline di build e ambienti CI/CD, è stata pubblicata su npm con un hook di preinstallazione malevolo capace di eseguire silenziosamente un infostealer nativo scritto in Rust, con build dedicate per Windows, macOS e Linux. Non serviva importare il pacchetto né lanciare un comando: la sola installazione bastava a far partire il payload.
Il caso, documentato da Socket, StepSecurity e SafeDep, si inserisce in una scia di attacchi alla supply chain npm che va avanti da fine 2025 — dal worm Shai-Hulud alla compromissione di chalk e debug, fino al trojan iniettato in Axios a marzo. Ma questa volta il bersaglio non è la massa di utenti finali: è l’ambiente di build stesso, dove risiedono le chiavi cloud, i token di deploy e il codice sorgente che un’infrastruttura CI può raggiungere.
Sei minuti per essere scoperti, troppo tardi per molti
Socket ha segnalato la release appena sei minuti dopo la pubblicazione. Chiunque, o qualsiasi sistema di build, avesse scaricato il pacchetto in quella finestra ha già eseguito il payload con tutti i permessi del processo di installazione. L’hook malevolo si attiva prima ancora che il pacchetto venga configurato, e non compare nella release precedente, la 8.13.0: il diff del pacchetto mostra due nuovi file sotto dist/, setup.js — un piccolo loader — e intro.js, che nonostante il nome non è affatto JavaScript ma un container di circa 7,8 MB che impacchetta tre binari nativi compressi in gzip, uno per Linux, uno per Windows e uno per macOS.
All’installazione, setup.js seleziona il binario per il sistema operativo host, lo scrive con un nome casuale nella directory temporanea di sistema, lo rende eseguibile e lo lancia in modalità detached nascondendone l’output. I file aggiunti erano presenti nel pacchetto pubblicato ma assenti nel codice sorgente pubblico: sia StepSecurity sia SafeDep, che hanno analizzato indipendentemente la release, riportano l’assenza di qualsiasi commit, tag o pull request corrispondente alla versione 8.14.0 nel repository GitHub, il cui ultimo tag ufficiale resta 8.13.0.
La versione compromessa è stata pubblicata direttamente su npm da un account maintainer legittimo, bypassando il normale flusso di rilascio del progetto — un forte indicatore di account npm compromesso o pipeline di build violata. Quale delle due ipotesi sia corretta non è ancora stato stabilito.
Cosa fa davvero l’infostealer
L’analisi di follow-up di Socket identifica il payload come un infostealer Rust, compilato per tutte e tre le piattaforme, che scandaglia la macchina dello sviluppatore alla ricerca di segreti e li invia a un server di raccolta via TLS. La lista dei bersagli è ampia e mirata esplicitamente agli sviluppatori:
- Credenziali cloud da AWS, Azure e Google Cloud, inclusi gli endpoint di metadata usati dai runner CI.
- Wallet di criptovalute e seed phrase da MetaMask, Phantom ed Exodus, oltre al vault del password manager Bitwarden.
- Password e cookie salvati nel browser, insieme alle sessioni di Discord, Slack, Telegram e Steam.
- File di configurazione di strumenti di coding AI — Claude Desktop, Cursor, Windsurf, VS Code e Zed — dove risiedono tipicamente chiavi API e credenziali dei server Model Context Protocol (MCP).
Il payload va oltre il furto ordinario di credenziali: su Linux, si collega alla libreria BPF del kernel ed è in grado di caricare in memoria un programma eBPF, un punto d’appoggio a livello kernel piuttosto che il semplice accesso userspace ai file su cui si basa il resto dello stealer. Sia StepSecurity sia SafeDep hanno segnalato questa capacità, anche se la funzione esatta del programma eBPF è ancora in fase di analisi. Le build Windows e macOS aggiungono controlli anti-debug, mentre la persistenza è garantita da un task pianificato nascosto su Windows, impostato per rilanciarsi ogni minuto, e da un LaunchAgent su macOS che si ricarica a ogni login.
I dettagli di comando e controllo restano cifrati nel binario e non sono emersi dall’analisi statica; il monitoraggio runtime di StepSecurity ha però intercettato il binario mentre contattava due indirizzi IP hardcoded e infrastruttura Tor — i primi indicatori di rete pubblicati per questa campagna.
Timeline e portata dell’incidente
Nell’arco di circa tre ore, l’attaccante ha pubblicato cinque release malevole — 8.14.0, 8.16.0, 8.17.0, 8.18.0 e 8.20.0 — intervallate da release pulite che i maintainer sembrano aver spinto come tentativo di rimedio. Il pacchetto jscrambler conta circa 15.800 download settimanali: una base d’utenza ben più contenuta rispetto ai grandi incidenti npm dell’ultimo anno, che hanno coinvolto pacchetti con miliardi di download settimanali complessivi. Ma per uno stealer pensato per colpire macchine di build, la portata non era l’obiettivo: lo era l’accesso.
Un dettaglio temporale rende il caso ancora più significativo: npm 12, rilasciato l’8 luglio 2026, appena tre giorni prima di questo incidente, ha disattivato di default l’esecuzione automatica degli script di installazione delle dipendenze. Su npm 12, un preinstall hook come questo non gira a meno che qualcuno non lo approvi esplicitamente. I client più datati, però, continuano a eseguirli automaticamente — ed è esattamente lì che l’attacco ha colpito.
La versione 8.15.0 ha da allora sostituito la 8.14.0 in cima all’elenco versioni di npm, pubblicata dallo stesso account maintainer e priva di qualsiasi alert di malware: nessuno script di installazione, nessun binario incluso. Ma la versione 8.14.0 non è stata rimossa da npm: resta scaricabile, quindi qualsiasi lockfile o comando ancorato a quella versione continua a installare lo stealer. Solo il pacchetto CLI principale è stato colpito; i plugin jscrambler per webpack, gulp, Metro e grunt sono rimasti sulle release pulite di giugno, senza hook di installazione.
Cosa fare adesso
- Abbandonare immediatamente la versione 8.14.0: passare alla 8.15.0, oppure fissare la 8.13.0 (release precedente all’incidente), ed eliminare jscrambler@8.14.0 da lockfile e cache.
- Verificare se la versione compromessa è stata installata: controllare lockfile e log dei package manager per jscrambler@8.14.0, e i log CI per qualsiasi esecuzione di dist/setup.js dall’11 luglio in poi. Il loader scrive il payload con un nome casuale nella directory temporanea, quindi non esiste un nome di binario fisso da cercare: bisogna incrociare i timestamp di installazione con i processi figli di Node e l’esecuzione nella directory temp. Su Windows controllare Task Scheduler per task nascosti; su macOS ispezionare ~/Library/LaunchAgents per plist sconosciuti.
- Se la 8.14.0 è stata eseguita su una macchina, trattare ogni segreto raggiungibile come rubato, non solo come esposto: ruotare chiavi cloud, token npm e GitHub, chiavi API di strumenti AI e MCP; revocare sessioni Discord, Slack, browser e Bitwarden; spostare eventuali fondi in criptovaluta da wallet presenti su quell’host. Bloccare i due IP di comando e controllo elencati di seguito.
La pulizia da parte del maintainer è stata rapida, ma uno stealer compie il proprio lavoro nei secondi immediatamente successivi all’installazione. La 8.14.0 resta su npm, e una build ancorata a quella versione, su un client datato che esegue ancora gli script di installazione, continua a eseguire il payload. Nel momento in cui la 8.15.0 ha raggiunto la cima della lista versioni, i segreti su ogni macchina che aveva già eseguito la 8.14.0 erano già compromessi.
Indicatori di compromissione
Pacchetto malevolo: jscrambler@8.14.0
SHA-256 dei file aggiunti e dei payload decompressi:
dist/setup.js: a742de963f14a92d24ebcbc7b44ac867e23a20d31d1b0094a13a4f83287f4e60
dist/intro.js: a41a523ef9517aab37ed6eea0ec881821bdcb7aefcb5c5f603adc7907f868c86
Payload Linux: fbbcf4d8f98168f78f5c0c47a9ae56d59ec8ac84a7c9ca6b797fedfb8d62d2bd
Payload Windows: b7ca95d1b23c8e67416a25cedf741de0917c2096bbc9d24649eea7853d054903
Payload macOS: c8fd47d36bdf7c825378593ab82ed8c24d1dc52e26b507812393e24e1d5201fd
Endpoint di rete osservati a runtime (StepSecurity):
C2 IP: 37.27.122[.]124
C2 IP: 57.128.246[.]79
Infrastruttura Tor: check.torproject[.]org, archive.torproject[.]org
Artefatti on-host:
File nascosto con nome casuale nella directory temp di sistema (.{random}, o .{random}.exe su Windows)
Task pianificato nascosto su Windows / LaunchAgent su macOS per la persistenza
Fonti: Socket.dev, StepSecurity, SafeDep.