Pacchetti npm compromessi: cosa è successo e come difendersi con un periodo di quarantena - init.d
EN

# Pacchetti npm compromessi: cosa è successo e come difendersi con un periodo di quarantena

Alessandro Corbelli~7 min
Table of Contents

Chi sviluppa in JavaScript installa librerie di terze parti decine di volte al giorno, quasi senza pensarci. Un comando, qualche secondo, e il progetto ha una nuova dipendenza - insieme a tutte le dipendenze di quella dipendenza. Negli ultimi mesi questo gesto quotidiano è diventato il canale di una serie di attacchi che hanno colpito librerie usate da milioni di progetti. La buona notizia è che una delle difese più efficaci è anche semplice da adottare: aspettare qualche giorno prima di installare le versioni appena pubblicate. Vediamo cosa è successo e come si configura questa protezione con pnpm.

Cos’è un attacco alla supply chain

Prima i termini. Il registro npm è l’archivio pubblico da cui si scaricano i pacchetti JavaScript: contiene milioni di librerie, pubblicate dai rispettivi manutentori. Un attacco alla supply chain (“catena di fornitura”) non colpisce direttamente il tuo progetto: colpisce una delle librerie che il tuo progetto usa. Se un malintenzionato riesce a pubblicare una versione modificata di una libreria diffusa, chiunque la installi in quel periodo esegue anche il suo codice.

Il punto debole è quasi sempre l’account del manutentore. Ottenute le sue credenziali - di solito con il phishing, cioè con email e siti contraffatti che imitano quelli veri - l’attaccante pubblica una nuova versione del pacchetto con dentro il codice malevolo. Per chi installa, quella versione è indistinguibile da un aggiornamento legittimo.

Cosa è successo, in breve

Gli episodi recenti danno la misura del problema.

A settembre 2025 un manutentore molto attivo è stato ingannato da una email di phishing che imitava il supporto di npm (il dominio contraffatto era npmjs.help). Con le sue credenziali sono state pubblicate versioni malevole di 18 pacchetti, tra cui chalk e debug: librerie minuscole ma presenti, direttamente o indirettamente, in una fetta enorme dell’ecosistema - nell’insieme contano miliardi di scaricamenti a settimana. Il codice iniettato dirottava transazioni di criptovalute nel browser. Le versioni compromesse sono rimaste online circa due ore prima della rimozione.

Poche settimane dopo è arrivato Shai-Hulud, un attacco di tipo nuovo: un worm, cioè un programma che si replica da solo. Partito dal pacchetto @ctrl/tinycolor, una volta installato cercava sulla macchina della vittima credenziali e token di accesso (npm, GitHub, provider cloud), li usava per pubblicare versioni infette dei pacchetti gestiti dalla vittima stessa, e così si propagava di manutentore in manutentore. I pacchetti coinvolti sono arrivati nell’ordine delle centinaia, e i segreti rubati venivano pubblicati in repository GitHub creati automaticamente. A fine novembre 2025 una seconda ondata (“Shai-Hulud 2.0”) ha prodotto decine di migliaia di repository malevoli.

Il fenomeno non si è fermato: nella primavera del 2026 i ricercatori hanno documentato nuove campagne che riprendono le stesse tecniche - tra cui una terza ondata del worm partita a fine aprile - e a giugno 2026 sono stati compromessi una trentina di pacchetti pubblicati sotto il namespace @redhat-cloud-services. Non è un elenco completo, e con ogni probabilità non sarà l’ultimo capitolo.

Perché basta “aspettare” per difendersi

C’è un dettaglio che ricorre in quasi tutti questi episodi: le versioni malevole vengono individuate e rimosse in fretta. Le versioni compromesse di chalk e debug sono durate un paio d’ore; il worm Shai-Hulud è stato identificato nel giro di una dozzina di ore. La comunità della sicurezza monitora il registro npm costantemente, e un pacchetto popolare che inizia a fare cose strane viene segnalato quasi subito.

Il problema è che in quelle poche ore i danni li subisce chi installa proprio in quel momento. E gli intervalli di versione usati nei package.json (il classico ^1.2.3, che accetta qualsiasi versione compatibile più recente) fanno sì che una versione appena pubblicata venga adottata automaticamente da chiunque installi da zero o aggiorni.

Da qui l’idea della quarantena (o cooldown): non installare mai versioni pubblicate da meno di X giorni. Non protegge da tutto - un attacco che resta inosservato più a lungo la supera - ma avrebbe bloccato la quasi totalità degli episodi visti finora, al costo di adottare gli aggiornamenti con qualche giorno di ritardo.

Come si configura con pnpm

pnpm (un gestore di pacchetti alternativo a npm, compatibile con lo stesso registro, che sa anche bloccare la versione di Node.js usata dal progetto) implementa questa idea con l’impostazione minimumReleaseAge, espressa in minuti. Si imposta nel file pnpm-workspace.yaml alla radice del progetto:

pnpm-workspace.yaml
# non installare versioni pubblicate da meno di 3 giorni
minimumReleaseAge: 4320

Da quel momento pnpm, per ogni dipendenza - incluse quelle transitive, cioè le dipendenze delle dipendenze - risolve la versione più recente che abbia almeno quell’età. L’impostazione esiste da pnpm 10.16. Un esempio provato mentre scrivevamo questo articolo: al 16 luglio 2026 l’ultima versione di @types/node è la 26.1.1, pubblicata l’8 luglio. Con una quarantena di 10 giorni (minimumReleaseAge: 14400), pnpm salta la 26.1.1 e installa la 26.1.0 del 1° luglio, dichiarandolo apertamente:

Terminal window
$ pnpm add @types/node
# ...
dependencies:
+ @types/node 26.1.0 (26.1.1 is available)

Nessun errore, nessun intervento manuale: la versione troppo recente non viene considerata, e pnpm segnala tra parentesi che esiste. Quando avrà superato la soglia, un normale aggiornamento la porterà nel progetto.

Il controllo non vale solo per le nuove installazioni: pnpm verifica anche il lockfile esistente, e se contiene una versione più giovane della soglia blocca l’installazione con un errore esplicito (ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION, provato anche questo). Se una dipendenza specifica deve poter arrivare subito - per esempio un pacchetto interno all’azienda, o una correzione di sicurezza attesa - si aggiunge un’eccezione:

minimumReleaseAge: 4320
minimumReleaseAgeExclude:
- "@nostra-azienda/*"

Quanto dev’essere lunga la quarantena? Da pnpm 11 l’impostazione è attiva di default a 1440 minuti (un giorno), che è già sufficiente per gli episodi lampo come chalk/debug. Un valore tra i 3 e i 7 giorni dà più margine, in cambio di aggiornamenti un po’ meno tempestivi: una scelta ragionevole per progetti che non inseguono l’ultima versione di tutto.

Le altre difese che pnpm attiva da solo

La quarantena funziona meglio insieme ad altre due protezioni, che nelle versioni recenti di pnpm sono già attive.

La prima riguarda gli script di installazione. Molti attacchi (Shai-Hulud incluso) si attivano tramite i lifecycle script, comandi che un pacchetto può eseguire automaticamente al momento dell’installazione. Da pnpm 10 questi script non vengono più eseguiti per le dipendenze, salvo quelle autorizzate esplicitamente (la chiave attuale è allowBuilds; nelle versioni precedenti si chiamava onlyBuiltDependencies):

pnpm-workspace.yaml
# solo questi pacchetti possono eseguire script di installazione
allowBuilds:
- esbuild

La seconda è il lockfile (pnpm-lock.yaml), il file che registra le versioni esatte installate. In CI e in produzione conviene installare con pnpm install --frozen-lockfile: se il lockfile non corrisponde al package.json l’installazione fallisce invece di risolvere versioni nuove, quindi nessun aggiornamento entra senza passare da una modifica revisionabile, lo stesso spirito con cui conviene ritagliare i permessi dei token usati dalla pipeline sul minimo necessario invece di lasciarli ad ampio raggio.

Chi usa npm non resta senza opzioni: il flag --before permette di installare solo versioni pubblicate prima di una certa data (npm install --before="2026-07-09"), anche se va passato a ogni comando; al momento non esiste un equivalente stabile del cooldown automatico di pnpm.

In sintesi

Gli attacchi alla supply chain di npm non sono più episodi isolati: tra la fine del 2025 e il 2026 hanno colpito librerie di uso comune, con un worm capace di propagarsi da solo tra i manutentori. Quasi tutte le versioni malevole, però, vengono rimosse entro poche ore o pochi giorni dalla pubblicazione. Imporre una quarantena alle versioni troppo recenti - con minimumReleaseAge di pnpm, un valore tra 1 e 7 giorni - elimina la finestra in cui si è più esposti, senza cambiare il modo di lavorare. Insieme al blocco degli script di installazione e al lockfile congelato nella pipeline che compila il progetto, è oggi una delle difese con il miglior rapporto tra costo e beneficio per chi sviluppa in JavaScript.

Fonti

Tux versione Gandalf, mascotte del blog init.d

init.d è il team di Alessandro Corbelli, sistemista Linux e sviluppatore backend con oltre vent’anni di esperienza. Progetta e gestisce infrastrutture cloud (Google Cloud, AWS, Azure), server farm e architetture ad alta disponibilità, e sviluppa software su misura in Laravel/PHP e Vue - dalla piattaforma di food delivery Take2Me ai gestionali dei nostri clienti. Su questo blog condividiamo note tecniche su Linux, sistemistica, sviluppo, DevOps ed e-commerce.


Altri articoli