Un token in .npmrc nascondeva malware nei plugin di Tailwind per npm

Un token in .npmrc nascondeva malware nei plugin di Tailwind per npm

Un progetto Next.js inviato a un freelance da un presunto cliente di Fiverr conteneva un token di npm che permetteva di scaricare una dipendenza privata, invisibile nel registro pubblico. Il codice si attivava dopo, quando lo sviluppatore avviava il server di sviluppo: una configurazione di Tailwind caricava un plugin, e il plugin richiedeva il pacchetto nascosto all’interno del processo di Node.js.

Lo sviluppatore che ha raccontato il caso su DEV Community non ha eseguito il progetto sul suo computer. Lo ha esaminato in un ambiente isolato in sola lettura e ha segnalato il pacchetto a npm. La manovra non dipendeva da un installer apertamente malevolo né da codice offuscato a prima vista. Il pezzo decisivo era un token in .npmrc, allegato al progetto e in grado di aprire l’accesso a un pacchetto che restituiva un errore 404 a chi non aveva le credenziali.

La portata va oltre quell’incarico: Corgea ha documentato una campagna con pacchetti pubblici che imitavano plugin di Animate.css per Tailwind, mentre pkgwarden ha descritto una rete di 29 pacchetti e un repository di prova tecnica per un’offerta di lavoro, preparato per consegnare il token. I rapporti non riportano esattamente lo stesso elenco né la stessa data di verifica; insieme mostrano una tattica che si adattava dopo le rimozioni.

Una catena che aspettava il server di sviluppo

Nel progetto ricevuto dal freelance, il file frontend/package.json dichiarava tailwind-animationlab come dipendenza di sviluppo. Il plugin era stato pubblicato su npm il 28 settembre, la stessa data in cui, secondo il post di DEV Community, era stata modificata per l’ultima volta la configurazione di Tailwind. Nessun componente dell’applicazione usava le sue classi: bastava che Tailwind lo caricasse.

Dentro il plugin, src/motion.js richiedeva @jasperquinn/postcss-motion-helpers. Quel pacchetto non era disponibile pubblicamente. Il file frontend/.npmrc, però, conteneva un token di accesso di npm. Durante l’installazione delle dipendenze, npm poteva usarlo per scaricare il pacchetto privato; poi, eseguendo npm run dev, Next.js caricava la configurazione di Tailwind, che a sua volta apriva il plugin e la sua dipendenza.

L’ordine conta: scaricare il pacchetto ed eseguirne il codice erano due momenti distinti. Corgea e pkgwarden spiegano che le versioni esaminate non avevano bisogno di script di installazione; il caricamento avveniva quando il server o il processo di build leggeva la configurazione. Per questo l’opzione --ignore-scripts non avrebbe fermato questa catena: evitava gli script del ciclo di vita, ma non impediva che la configurazione richiedesse il plugin all’avvio.

flowchart TD
A["Progetto con .npmrc"] --> B["npm install usa il token"]
B --> C["Scarica il pacchetto privato"]
C --> D["npm run dev avvia Next.js"]
D --> E["Tailwind carica la sua configurazione"]
E --> F["Il plugin richiede il pacchetto nascosto"]
F --> G["Codice dentro il processo Node.js"]

La catena spiega perché una revisione limitata al codice dell’applicazione poteva non trovare nulla. Il freelance ha detto che il backend, i modelli di MongoDB, le chiamate a Gemini e i componenti sembravano puliti. La dipendenza pericolosa entrava da fuori, non dal codice visibile in quei file, e veniva eseguita al caricamento della configurazione.

Un’offerta di lavoro come canale di consegna

L’incarico su Fiverr aveva segnali che, presi insieme, risultavano sospetti: non c’erano screenshot né log degli errori che si sarebbero dovuti correggere, il primo file era una presentazione per la revisione interna e per gli investitori e il codice arrivava in uno ZIP condiviso su Google Drive, non da un repository GitHub. Inoltre il README descriveva un’applicazione React con Vite e Clerk, mentre il progetto conteneva Next.js 16 e un login basato su JWT. Eppure le istruzioni ripetevano che bisognava installare e avviare l’applicazione.

Pkgwarden ha documentato un’altra variante dell’esca: una prova tecnica attribuita a «Nodveta», pubblicata su Torre.ai, che portava a un repository privato di GitHub. Lì il file web/.npmrc conteneva il token e il progetto includeva file destinati agli agenti di programmazione. Secondo pkgwarden, AGENTS.md e CLAUDE.md chiedevano all’agente di consultare documentazione dentro node_modules, spingendo così a installare le dipendenze. Il bersaglio, quindi, poteva essere sia la persona candidata sia l’agente che lavorava nel suo ambiente.

La somiglianza tra l’incarico descritto su DEV Community e lo schema dei falsi colloqui segnalato da pkgwarden non dimostra chi ci fosse dietro il caso di Fiverr. L’autore del post ha detto espressamente di non poter attribuire la campagna a un attore preciso. Anche pkgwarden ha evitato di assegnarla a un gruppo e l’ha descritta come coerente con il metodo noto come «Contagious Interview».

Rimozioni rapide, sostituzioni e cifre diverse

Secondo la cronologia di DEV Community, l’autore ha segnalato tailwind-animationlab a npm il 29 ottobre, verso mezzogiorno ora dello Sri Lanka. Quella stessa notte npm ha sostituito quel pacchetto e tailwind-keyframes con versioni di sicurezza e ha rimosso l’helper privato @jasperquinn/postcss-motion-helpers. Poche ore dopo è comparso un altro helper privato; npm ha rimosso anche quello il 30 ottobre. GitHub Trust & Safety ha comunicato all’autore che «one or more violations of npm's Open-source Terms have occurred».

one or more violations of npm's Open-source Terms have occurred

— GitHub Trust & Safety

La rimozione non ha chiuso l’attività. Il 2 novembre l’autore di DEV Community ha trovato una versione 2.0.10 di tailwind-kinetics che dipendeva da un helper diverso, oltre ad altri due pacchetti privati non visibili nel registro pubblico. Anche Corgea, nel suo aggiornamento del 30 ottobre, ha registrato il passaggio a nuovi scope privati e a pacchetti pubblici pubblicati o aggiornati dopo le prime misure di npm.

I conteggi dipendono dal perimetro e dal momento di ciascuna indagine. DEV Community ha individuato almeno 19 pacchetti aggiuntivi con codice basato sullo stesso modello, oltre a quelli descritti nel suo caso. Corgea ha esteso il suo elenco a 43 pacchetti pubblici e sette dipendenze private legate allo schema. Pkgwarden, invece, ha seguito un gruppo di 29 plugin e 40 versioni, e ha segnalato che 26 di quei pacchetti erano ancora installabili il 16 ottobre. Non sono cifre intercambiabili: i rapporti coprono date, pacchetti e sottogruppi diversi.

Cambia anche ciò che si poteva verificare del payload. I pacchetti pubblici esaminati da Corgea e pkgwarden sembravano plugin funzionanti e non contenevano script di installazione né codice offuscato; il pezzo privato era quello che non si poteva ispezionare dal registro pubblico. LPM Firewall, analizzando [email protected], ha detto che il codice disponibile mostrava un percorso di generazione del CSS, ma che il comportamento dell’helper importato restava da chiarire perché non ne aveva il codice. I rapporti, quindi, descrivono una catena progettata per caricare codice nascosto; non verificano quali azioni concrete eseguisse quel codice sul computer.

Il costo di fidarsi di un progetto altrui

La questione è particolarmente delicata per chi accetta lavori da freelance o prove tecniche. Per valutare l’incarico bisogna eseguire un’applicazione che non si controlla; qui, farlo sul computer di tutti i giorni avrebbe dato al codice lo stesso accesso ai file che aveva l’utente. L’autore di DEV Community ha avvertito che campagne simili avevano cercato password salvate, token di API e wallet di criptovalute, ma non ha affermato di aver osservato queste azioni nel payload privato di questo caso.

Per le aziende, il rischio riguarda anche gli ambienti di sviluppo e gli agenti di programmazione. Pkgwarden ha segnalato che la documentazione aggiunta al repository era pensata per orientare gli strumenti di IA verso l’installazione e l’avvio. Un agente con accesso all’ambiente del candidato non rende affidabile un repository dubbio: può seguire istruzioni che innescano lo stesso caricamento che eseguirebbe una persona.

Lo schema lascia una tensione pratica: i controlli concentrati sugli script di installazione non coprono il codice che viene eseguito quando uno strumento carica una configurazione. E non basta nemmeno che il pacchetto pubblico sembri piccolo, funzionante e leggibile, se dipende da un pacchetto privato che l’analisi esterna non può recuperare. In questo caso l’installazione autenticata nascondeva il legame tra i due componenti, e l’avvio di Next.js completava l’esecuzione.

L’autore del caso ha evitato di installare il progetto sul suo computer e ha usato un ambiente isolato per leggere i file senza eseguire comandi. DEV Community ha raccomandato di controllare con particolare attenzione i file di configurazione che vengono caricati durante lo sviluppo, di considerare un segnale d’allarme un token in un .npmrc ricevuto insieme a un progetto altrui e di verificare le dipendenze sconosciute. A chi avesse già eseguito una prova tecnica colpita, pkgwarden ha consigliato di considerare il computer compromesso e di ruotare le credenziali a cui poteva accedere.

Il punto di controllo non era una funzione visibile dell’applicazione né un installer che partisse subito: era la combinazione di una credenziale inclusa nel progetto, di un pacchetto privato inaccessibile a terzi e di una configurazione che lo caricava all’avvio.

Fonti: DEV Community, Corgea, pkgwarden, LPM Firewall