№ xlii L'Almanacco di GST · EN IT

Enrico·rubbo.li

Tech · Longevity · Mercati · Opinioni Enrico Rubboli, propr. Dubai, UAE
essay August 7, 2026 16 min

Shai-Hulud: il malware con una firma valida

Il 4 agosto qualcuno è entrato nell’account GitHub del maintainer di keyv, una piccola libreria di caching a cui quasi nessuno pensa, ma da cui dipendono circa 127 milioni di download settimanali su npm.

Vale la pena descrivere ciò che è successo dopo rispettando la velocità reale degli eventi. L’attaccante ha pubblicato una versione compromessa. Ogni macchina che l’ha installata ha eseguito uno script prima di qualsiasi altra operazione; questo script ha raccolto le credenziali del sistema, tra cui figuravano i token di pubblicazione di npm. Il malware ha utilizzato i token rubati per ripubblicare l’intero namespace della vittima, al ritmo di circa un pacchetto al secondo, inserendo in ognuno lo stesso payload. Poi ha ripetuto l’operazione partendo dal set successivo di credenziali sottratte. I ricercatori lo hanno osservato spostarsi da un’organizzazione all’altra ogni due-sette minuti. Nel giro di mezz’ora aveva raggiunto nove organizzazioni distinte, tra cui Deliveroo, Qlik e ServiceTitan, nessuna delle quali aveva alcun legame con le altre se non la condivisione del registro dei pacchetti.[1]

I conteggi variano a seconda di chi effettuava le misurazioni e in quale momento, come è normale che sia quando l’oggetto dell’osservazione è in continua crescita. Aikido ha stimato 868 pacchetti distribuiti su 1.381 versioni. SafeDep ha verificato 353 versioni compromesse su 79 nomi di pacchetti, ipotizzando un impatto ancora più esteso. Altri sistemi di tracciamento hanno visto comparire dai 50 ai 100 nuovi pacchetti ogni pochi minuti, superando quota 1.280.[1][2] Possiamo parlare di un migliaio di pacchetti e due miliardi di installazioni mensili, mettendo in conto che la cifra era già obsoleta nel momento stesso in cui veniva pubblicata.

C’è un dettaglio, però, a cui non riesco a smettere di pensare. Quelle versioni malevole erano, dal punto di vista crittografico, del tutto legittime. Erano state compilate e pubblicate attraverso le pipeline di continuous integration degli stessi maintainer. Erano corredate da un’attestazione di provenienza valida. Se si fosse verificata la firma, questa sarebbe risultata corretta.

Cosa viene effettivamente eseguito quando si digita install

Partiamo dal meccanismo, perché è proprio il meccanismo a costituire il nocciolo della questione.

Quando si esegue npm install, ai pacchetti è consentito lanciare degli script sulla macchina come parte del processo di installazione. Non si tratta di una vulnerabilità. È una funzionalità documentata, e molti pacchetti legittimi vi fanno affidamento per compilare codice nativo o completare la configurazione iniziale. L’hook del ciclo di vita sfruttato da questo worm è preinstall, che si attiva prima ancora che i contenuti del pacchetto vengano posizionati, e molto prima che un essere umano possa plausibilmente ispezionare ciò che è stato scaricato.

Il payload in sé non va tanto per il sottile. L’hook preinstall scarica il runtime Bun e lo utilizza per eseguire uno stealer pesantemente offuscato di circa 728 kilobyte.[2] Questo programma malevolo setaccia la macchina alla ricerca di qualsiasi cosa garantisca un accesso: token di autenticazione di npm conservati nel file .npmrc, token della CLI e di sessione di GitHub, credenziali AWS, token di HashiCorp Vault, configurazioni di Kubernetes e wallet di criptovalute.

A questo punto va notato il ciclo che trasforma questa operazione in un worm anziché in un semplice furto. Le credenziali sottratte sono le stesse di cui il malware ha bisogno per diffondersi. Un token di pubblicazione di npm rappresenta al tempo stesso il bottino e il carburante. Ogni macchina di sviluppo e ogni runner di continuous integration che installa un pacchetto avvelenato diventa un punto di lancio per l’ondata successiva; per giunta, i runner di continuous integration sono l’ospite ideale, poiché contengono credenziali di produzione, operano in modo non presidiato e nessuno si mette a controllare i log di installazione alle tre del mattino.

Ecco come si passa da un singolo account compromesso a nove organizzazioni in trenta minuti. Non attraverso una sofisticata catena di exploit, ma perché l’ecosistema è strutturato in modo tale che installare un software significa eseguire il codice del suo autore, e perché l’elemento che vale la pena rubare è proprio quello che permette all’infezione di proseguire.

La firma era valida

È qui che la vicenda smette di essere una normale violazione e inizia a farsi interessante.

Negli ultimi anni, il settore ha agito in modo responsabile riguardo alla sicurezza della supply chain. Abbiamo introdotto la firma dei pacchetti. Abbiamo sviluppato l’attestazione di provenienza, in modo che un pacchetto possa dimostrare di essere stato generato da una specifica pipeline di build a partire da un codice sorgente ben preciso. npm supporta il trusted publishing, un sistema che permette a un progetto di pubblicare dal proprio flusso di continuous integration utilizzando un token di identità a breve scadenza, anziché un segreto a lungo termine che rischia di trapelare. Dal punto di vista ingegneristico si tratta di un autentico passo avanti rispetto al passato, e voglio riconoscere il giusto merito a chi lo ha realizzato.

Tutto questo non ha fermato l’attacco.

La dimostrazione più evidente si è avuta a maggio, durante un’ondata di infezioni della stessa famiglia di malware che ha colpito i progetti TanStack. L’attaccante ha estratto i token OIDC di GitHub Actions direttamente dalla memoria del runner e li ha usati per pubblicare sfruttando la relazione di trusted publisher della stessa TanStack. Il risultato sono state 84 versioni malevole distribuite su 42 pacchetti, ognuna dotata di una valida attestazione di provenienza SLSA di Livello 3.[3] L’apparato di sicurezza della supply chain ha esaminato il malware e lo ha certificato come autentico, perché, in base alla sua stessa definizione, lo era.

Questa lezione ha una valenza generale e merita di essere formulata con precisione. Un’attestazione di provenienza dimostra quale pipeline ha prodotto un artefatto. Non dimostra, tuttavia, che quella pipeline fosse ancora sotto il controllo delle persone che l’hanno creata. Si tratta di due affermazioni diverse, e solo una delle due è quella che interessa davvero a chiunque. Abbiamo trascorso anni a imparare come verificare la domanda meno importante, semplicemente perché era l’unica a cui sapevamo rispondere con la matematica.

Ho già esposto una variante di questo ragionamento in passato, a proposito di una tecnologia diversa. Un hardware wallet garantisce che un segreto non lasci mai il dispositivo; il difetto di entropia del Coldcard di cui ho scritto la scorsa settimana ha onorato pienamente quella garanzia, eppure i fondi sono spariti lo stesso, poiché l’attaccante non aveva bisogno che il segreto uscisse dal dispositivo se poteva semplicemente indovinarlo. La garanzia è stata rispettata. Ma era la garanzia sbagliata. In questo caso, le firme rappresentano la stessa tipologia di errore: una promessa mantenuta alla perfezione, ma relativa alla proprietà sbagliata.

Tutto questo non significa che la firma sia inutile. Significa che la firma risponde alla domanda «da dove proviene questo codice?», mentre noi abbiamo interpretato la risposta come «questo codice è sicuro?».

Tre anni della stessa lezione, appresa dall’attaccante

Ciò che rende istruttiva questa famiglia di malware è la possibilità di osservarla mentre si adatta, ondata dopo ondata, a qualsiasi difesa sia stata messa in campo contro la variante precedente.

Il primo Shai-Hulud è arrivato a settembre 2025. Il paziente zero è stato un pacchetto chiamato rxnt-authentication: il malware veniva eseguito da uno script di postinstall ed esfiltrava i dati raccolti verso repository GitHub pubblici creati ad hoc. In quattro giorni sono stati compromessi oltre 200 pacchetti e più di 500 versioni, spingendo la CISA a diramare un avviso di sicurezza.[4]

Nel novembre 2025 si è assistito alla seconda ondata, che presentava già dei cambiamenti. L’esecuzione si è spostata da postinstall a preinstall, che si attiva prima e lascia meno margine di manovra ai difensori. Il payload arrivava tramite uno script setup_bun.js che rilasciava un file offuscato chiamato bun_environment.js, sfruttando un runtime legittimo per eseguire codice malevolo, una tecnica che rende l’analisi statica molto più complessa. Quell’ondata ha inserito backdoor in 796 pacchetti unici.[5]

L’adattamento del maggio 2026 avrebbe dovuto fare molto più scalpore di quanto non abbia fatto. Gli attaccanti hanno smesso di tentare di aggirare il trusted publishing e hanno iniziato a rubare l’identità su cui esso si basa, motivo per cui i pacchetti TanStack sono risultati correttamente attestati. Durante la stessa ondata, il malware ha imparato a persistere in una nuova posizione, un dettaglio che merita una sezione a parte.

E ora arriviamo ad agosto, con l’applicazione di tutte le tattiche accumulate: preinstall, Bun, offuscamento, furto di credenziali, ripubblicazione dei namespace e una velocità tale che l’incidente ha superato i tempi tecnici di segnalazione.

Ogni generazione rappresenta una risposta diretta alla difesa precedente. Non si tratta di una serie di incidenti scollegati. È un singolo avversario che procede per iterazioni contro di noi alla luce del sole, e lo fa più velocemente di quanto l’ecosistema riesca a modificare le proprie impostazioni predefinite.

Ora si annida nell’editor

L’ondata di maggio ha introdotto un elemento che ritengo genuinamente inedito, e rappresenta il dettaglio più rilevante per il modo in cui molti di noi lavorano oggi.

Il malware inserisce meccanismi di persistenza nei file di configurazione letti automaticamente dagli assistenti alla programmazione basati sull’IA. Aggiunge degli hook al file .claude/settings.json per Claude Code e dei task a .vscode/tasks.json con l’impostazione runOn: folderOpen per VS Code, in modo che il payload venga rieseguito ogni volta che si apre un progetto. Installa inoltre un demone di sistema, un LaunchAgent su macOS o un’unità systemd su Linux, per sopravvivere a un riavvio. Tutto questo resiste al comando npm uninstall, perché nulla di tutto ciò risiede più nella cartella node_modules.[6]

Dopodiché compie l’azione che trasforma un fastidio in un’infestazione. Scansiona il filesystem alla ricerca di ogni altra configurazione di Claude Code e VS Code e inietta gli stessi hook in ciascuna di esse. Basta una dipendenza avvelenata in un singolo progetto e il livello di persistenza si insedia in ogni repository presente su quella macchina.[6]

Fermiamoci a riflettere su questo schema. L’assistente alla programmazione, lo strumento che abbiamo introdotto di proposito affinché potesse leggere e intervenire sui nostri progetti, diventa il vettore di reinfezione. Ha accesso ai file, viene eseguito in automatico, gode di fiducia per definizione e la sua configurazione è un elemento che quasi nessuno pensa di controllare. In un saggio di un paio di settimane fa ho scritto di sistemi autonomi in grado di raggiungere spazi che non erano mai stati loro concessi, e ho sostenuto che il contenimento tende a essere una messa in scena, poiché la falla si trova a monte, nelle scelte umane. Questa è la stessa storia vista dalla prospettiva opposta. Non c’è nessun agente autonomo che compie azioni brillanti. Si tratta di un banale malware che ha notato l’installazione di un software dotato di ampi permessi e di un file di configurazione, e ci si è accasato.

La parte che irriterà le persone come me

L’infrastruttura di comando e controllo per l’ondata di agosto passa attraverso uno smart contract su Ethereum, a un indirizzo reso noto dai ricercatori, che l’attaccante utilizza per ruotare l’infrastruttura senza mai modificare il payload.[1]

Ho trascorso la mia carriera a sostenere la necessità di sistemi che nessuna singola entità possa spegnere. Non è uno slogan per me, è ciò che costruisco. Perciò non farò finta che questo sia un problema di qualcun altro. Un livello di coordinamento resistente alla censura, sempre disponibile e permissionless è utile a chi gestisce un worm esattamente quanto lo è per chiunque altro, e per le medesime ragioni. Non si può sequestrare il dominio, perché non c’è alcun dominio. Non si può notificare un ordine di rimozione a uno smart contract.

La posizione più onesta non consiste nel negare il compromesso o nel concludere che l’infrastruttura permissionless sia stata un errore. Consiste nel prendere atto che la stessa proprietà si manifesta su entrambi i lati della medaglia, e che chiunque affermi che la propria tecnologia ha solo impieghi positivi sta cercando di vendere qualcosa. Ho fatto la stessa ammissione a proposito dei pesi dei modelli aperti, e preferisco mantenere una certa coerenza anziché farlo solo quando fa comodo.

Perché «ruotare i token» non è sufficiente

Il consiglio standard dopo un incidente di furto di credenziali è quello di ruotare tutto. In questo caso, però, tale suggerimento risulta pericolosamente incompleto, e fermarsi a questa versione ridotta può dare un falso senso di sicurezza.

Il malware installa un watcher per monitorare la revoca delle credenziali. A chi interviene si consiglia di rimuovere prima questo watcher, poiché ruotare i token mentre è ancora in esecuzione non è un’operazione pulita.[2] Nel frattempo, la persistenza si annida nella configurazione dell’editor e in un demone di sistema, per cui rimuovere il pacchetto non serve a nulla.

Si tratta di un esempio specifico di un concetto che ho esposto in termini più generali in un articolo precedente: i consigli che presuppongono un ambiente pulito e collaborativo falliscono proprio quando l’ambiente non è né l’uno né l’altro. Qualsiasi macchina o runner che abbia installato una versione colpita dovrebbe essere considerato come totalmente compromesso a livello di credenziali, non in modo parziale, e l’ordine delle operazioni è fondamentale.

Alla base di tutto c’è un dato progettuale che nessuno vuole guardare in faccia. Quando si installa un pacchetto con una manciata di dipendenze, non si sta accordando fiducia a una manciata di pacchetti. Si sta accordando fiducia a ogni account dei maintainer nell’albero transitivo, oltre che alla sicurezza delle piattaforme su cui quegli account risiedono, e alle pipeline di continuous integration che li pubblicano. Si tratta di una superficie di fiducia composta da migliaia di esseri umani e dai loro token di sessione, e il comando preinstall garantisce di default a ciascuno di loro l’esecuzione arbitraria di codice sulla macchina. Noi lo chiamiamo «aggiungere una dipendenza».

Cosa regge davvero

Le mitigazioni efficaci sono poco appariscenti, uno schema in cui mi imbatto continuamente quando scrivo di sicurezza.

Disattivare gli script del ciclo di vita. Installare con --ignore-scripts, e abilitare questa opzione nel proprio progetto o nella configurazione globale di npm, elimina l’intero percorso di esecuzione di preinstall da cui dipende questa famiglia di malware. Ciò interrompe il funzionamento di alcuni pacchetti che hanno effettivamente bisogno di compilare codice nativo, ma a questi si può concedere l’autorizzazione in modo esplicito. La maggior parte dei team non ha mai nemmeno preso in considerazione questa ipotesi, ed è proprio questo il punto.

Bloccare le versioni di ciò che si installa. I file di lock, l’uso di versioni esatte e un periodo di attesa prima di adottare le nuove release costano pochissimo e rimuovono la finestra temporale in cui una versione appena avvelenata può arrivare in automatico. Un worm che ripubblica un namespace al ritmo di un pacchetto al secondo è pericoloso solo per chi utilizza strumenti configurati per scaricare sempre l’ultima novità senza fare domande.

Separare le credenziali di pubblicazione dalle macchine di sviluppo. Il motivo per cui il portatile di un singolo sviluppatore può avvelenare un intero namespace è che la stessa macchina contiene il token di pubblicazione, le credenziali cloud e un editor che esegue script arbitrari scaricati dalla rete. GitHub si è mosso nella giusta direzione, richiedendo l’autenticazione a due fattori per la pubblicazione in locale e riducendo la durata dei token a sette giorni.[3] Questo riduce il raggio d’azione. Tuttavia non chiude il percorso OIDC sfruttato dall’ondata di maggio, e non va confuso con la soluzione definitiva del problema.

Infine, mantenere completamente fuori dalla macchina gli elementi che non devono essere spostati per alcun motivo. Questo worm ruba i wallet di criptovalute insieme alle credenziali cloud, e l’unica categoria di segreti che ne è uscita indenne è quella che non si trovava affatto sulla macchina. Una chiave che risiede nell’hardware e firma le transazioni senza mai essere esportata non può essere sottratta da uno script di preinstall, per quanto quello script possa aver preso il controllo del computer. È questo il fondamento del discorso sul custodire correttamente le proprie chiavi, e rappresenta l’unica garanzia di questa vicenda che ha retto esattamente come promesso.

La scomoda sintesi è che le nostre difese della supply chain sono eccellenti nel dimostrare che un pacchetto è ciò che dichiara di essere, ma sono pressoché inutili per stabilire se chi lo dichiara sia ancora la persona che crediamo. L’identità è l’anello debole alla base di tutta la crittografia, e l’identità non è altro che un account GitHub con un token di sessione sul portatile di qualcuno.

Abbiamo costruito una macchina in grado di dimostrare da dove proviene il codice, e ci siamo abituati a interpretare questa informazione come una garanzia di sicurezza.


1. Aikido Security. (2026). Keyv and friends compromised in active Shai-Hulud supply chain attack. https://www.aikido.dev/blog/keyv-and-friends-compromised-in-npm-supply-chain-attack · BleepingComputer. (2026). Massive ChainDrop npm supply-chain attack infects hundreds of packages. https://www.bleepingcomputer.com/news/security/massive-chaindrop-npm-supply-chain-attack-infects-hundreds-of-packages/

2. SafeDep. (2026). npm worm poisons 400+ packages across nine organisations. https://safedep.io/keyv-npm-supply-chain-compromise/ · The Hacker News. (2026). Keyv-linked npm worm poisons hundreds of packages, plants Claude Code and VS Code hooks. https://thehackernews.com/2026/08/keyv-linked-npm-worm-poisons-hundreds.html

3. Snyk. (2026). Mini Shai-Hulud hits AntV: 300+ malicious npm packages published via compromised maintainer account. https://snyk.io/blog/mini-shai-hulud-antv-npm-supply-chain-attack/ · ReversingLabs. Will new npm security measures stop the next Shai-Hulud? https://www.reversinglabs.com/blog/npm-security-shai-hulud

4. Cybersecurity and Infrastructure Security Agency. (2025). Widespread supply chain compromise impacting npm ecosystem. https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem · Unit 42, Palo Alto Networks. (2025). “Shai-Hulud” worm compromises npm ecosystem in supply chain attack. https://unit42.paloaltonetworks.com/npm-supply-chain-attack/

5. Datadog Security Labs. (2025). The Shai-Hulud 2.0 npm worm: analysis, and what you need to know. https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/

6. Sonar. (2026). Mini Shai-Hulud targets AI coding agents. https://www.sonarsource.com/blog/mini-shai-hulud-targets-ai-coding-agents/ · Akamai. (2026). Mini Shai-Hulud: the worm returns and goes public. https://www.akamai.com/blog/security-research/mini-shai-hulud-worm-returns-goes-public