
Scanner per l'attacco alla supply chain keyv/cacheable: rileva pacchetti npm compromessi, verifica gli hash dei payload e individua implant di persistenza in modalità repo e host.
English | Português
Scanner autonomo per l'incidente della supply chain keyv/cacheable ("Shai-Hulud: Here We Go Again", 4 agosto 2026) — oltre 440 pacchetti npm compromessi da un worm auto-propagante che ruba le credenziali cloud/CI e impianta la persistenza con un dead-man's switch.
Rileva, in pochi minuti e senza installare nulla:
package-lock.json, npm-shrinkwrap.json, yarn.lock (v1 e Berry), pnpm-lock.yaml e bun.lock — incluse le dipendenze transitive, con l'intera catena (es. eslint → file-entry-cache → flat-cache → [email protected]);node_modules (nome + hash SHA-256 degli artefatti noti);SUSPECT, mai confermate senza hash;.claude/settings.json e .vscode/tasks.json, artefatti temporanei (bun-dl-*);keyv/cacheable è stato compromesso; l'attaccante ha pubblicato nuove versioni con un hook "preinstall": "node setup.mjs" — codice che viene eseguito prima dell'installazione del pacchetto, con i privilegi di chi ha lanciato npm install.setup.mjs scarica il runtime Bun da GitHub ed esegue il payload al suo interno — evasione contro gli strumenti che monitorano solo i processi node.Math_Symbol.js (~728 KB, offuscato) ruba le credenziali: metadati delle istanze AWS, chiavi AWS/GCP/Azure, token Vault, service account Kubernetes, segreti di GitHub Actions, token npm, più una scansione generica con regex per chiavi private e bearer token su disco.npm install/npm ci con gli script di lifecycle abilitati dal 2026-08-04 09:35 UTC. Con --ignore-scripts, l'hook non è stato eseguito..claude/settings.json (SessionStart) e .vscode/tasks.json (folderOpen) che eseguono il loader quando la cartella clonata viene aperta — niente npm install, nulla installato. Questo include chi ha clonato il repo per investigare l'incidente e gli agenti AI di coding che hanno aperto la directory — uno dei primi casi pubblici di hook di agenti AI (.claude/) usati come vettore di supply chain.L'impianto installa un "watcher" (gh-token-monitor) mantenuto attivo da un LaunchAgent (macOS) o da un servizio utente systemd + loginctl enable-linger (Linux). Ogni 60 secondi valida il token GitHub rubato contro l'API. Finché il token funziona, non succede nulla. Quando la risposta diventa 4xx — cioè nel momento in cui revochi il token — esegue, via eval, il contenuto di ~/.config/gh-token-monitor/handler: un comando arbitrario definito da remoto dall'attaccante. Le analisi pubbliche non sanno cosa contenga — potrebbe essere distruzione di dati, re-impianto, ransomware o nulla. Il rischio non è valutabile; è per questo che l'ordine di risposta è assoluto.
Tre proprietà che cambiano la risposta:
host.[email protected] è stato distribuito con un'attestazione SLSA che ha superato i controlli. La provenienza attesta l'integrità della build, non della sorgente: il workflow legittimo ha compilato codice già trojanizzato.package.json (l'hook preinstall) e in due nuovi file aggiunti al pacchetto (setup.mjs, Math_Symbol.js).eslint → file-entry-cache → flat-cache → keyv. È per questo che lo scanner mostra la catena in ogni riscontro.scan.mjs ha le seguenti proprietà — importanti per chiunque stia rispondendo a un incidente di supply chain:
npm install. Fai l'audit dell'intero scan.mjs in 15 minuti prima di eseguirlo.--update (scarica un manifest IOC aggiornato), esplicita e opzionale.docker run --network=none o una macchina isolata: basta copiare scan.mjs + iocs.json.Requisito: Node.js ≥ 18 (qualsiasi macchina con npm ce l'ha già). Scarica i due file — scan.mjs + iocs.json — e basta: nessuna installazione.
Attenzione: se hai clonato l'intero repository, la cartella
fixtures/contiene IOC inerti usati nei test (nomi e versioni reali, contenuto fittizio — nessun malware). Lo scanner la salta automaticamente e lo segnala nell'output; i riscontri da essa compaiono solo se la scannerizzi apposta.
Ci sono due modalità di esecuzione che rispondono a domande diverse, ed è questo che decide dove eseguirlo:
repo legge i lockfile e node_modules — e i lockfile vivono in git, quindi può essere centralizzata: una persona scansiona tutti i repository dell'azienda.host cerca l'impianto (watcher, LaunchAgent/systemd, hook IDE), che vive sulla macchina dove il codice è stato eseguito — non è in git e non può essere centralizzata.node scan.mjs repo /folder/with/all/the/repos --json=result.json --html=report.html
Risponde alla domanda "quali progetti sono esposti" in pochi minuti, senza coinvolgere nessuno. Accetta più percorsi; percorre le sottodirectory (monorepo e workspace inclusi).
Per ogni progetto con un riscontro, identifica chi lo ha toccato dal 2026-08-04 09:35 UTC (git log, log CI). Queste persone eseguono, sulla propria macchina:
node scan.mjs # current directory + host, in ~30 seconds
Nel perimetro: chiunque abbia (a) eseguito npm install/npm ci nella finestra; oppure (b) semplicemente clonato e aperto la cartella in VS Code o in un agente AI — il Vettore B non richiede installazione.
Poiché il costo è di ~30 secondi e l'imbuto può perdere (un clone vagante, un progetto personale), il messaggio interno più sicuro è: ogni sviluppatore esegue node scan.mjs una volta e invia il --json/--html ad AppSec. L'invio è manuale per design — lo scanner non ha telemetria (zero egress).
Priorità massima: è qui che vivono le credenziali più preziose. Qui lo scanner ha due ruoli diversi — uno per il passato, uno per il futuro:
Triaging del passato — NON scansionare il runner per decidere. Alla domanda "questo runner è stato colpito?" non risponde una scansione: se un job ha installato una versione compromessa senza --ignore-scripts dal 08-04, le credenziali sono già state rubate in quel momento, e l'host del runner raramente conserva prove (i runner effimeri distruggono il container alla fine del job; il watcher si auto-ripulisce in ~24h). A rispondere sono i lockfile del Passo 1 e i log CI. Se la risposta è "sì, ha installato": ricostruisci il runner e ruota i suoi segreti — i runner sono effimeri, non c'è motivo di pulirli.
Prevenzione per il futuro — SÌ, eseguilo nella pipeline. Aggiungi lo scanner come step di build, in modalità repo, dopo il checkout e prima di npm install. Non esamina l'host del runner — esamina il codice che sta per essere installato, e il codice di uscita fa fallire la build prima che il preinstall malevolo abbia la possibilità di essere eseguito:
# example (GitHub Actions / GitLab CI — adapt):
- run: node scan.mjs repo . --json # exit 0 clean · 1 findings · 2 COMPROMISED
- run: npm ci --ignore-scripts # only runs if the previous step passed
node scan.mjs # scan the current directory + the host
node scan.mjs repo /path/a /path/b
node scan.mjs host # persistence/implants on the machine only
node scan.mjs repo . --json=result.json --html=report.html
node scan.mjs --update # update iocs.json (the only network operation)
--html produce un report autonomo con timestamp, hostname, versione del manifest IOC e lo SHA-256 dello scanner stesso — utilizzabile come allegato a una notifica di incidente e come traccia di audit.
Non revocare o ruotare ancora alcuna credenziale — è il grilletto della trappola. La sequenza:
1. ISOLA — stacca la macchina dalla rete. È sicuro: senza risposta HTTP non c'è 4xx, la trappola non scatta e l'exfiltrazione si ferma. Non spegnere (la memoria volatile è una prova).
2. PRESERVA — prima di eliminare qualsiasi cosa (il watcher si autodistrugge in ~24h):
mkdir -p /tmp/evidence && cp -r ~/.config/gh-token-monitor /tmp/evidence/ 2>/dev/null
cp /tmp/gh-token-monitor.*.log /tmp/evidence/ 2>/dev/null
shasum -a 256 /tmp/evidence/* 2>/dev/null
Il file handler è il comando dell'attaccante che verrebbe eseguito — non eseguirlo, non incollarlo in una shell; trattalo come testo inerte. Il file started_at delimita la finestra di esposizione (l'auditor e il regolatore te lo chiederanno).
3. ERADICA — uccidi prima il processo del watcher, poi:
# macOS
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
rm -f ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
# Linux
systemctl --user disable --now gh-token-monitor.service
loginctl disable-linger "$USER"
rm -f ~/.config/systemd/user/gh-token-monitor.service
# both
rm -rf ~/.config/gh-token-monitor ~/.local/bin/gh-token-monitor.sh /tmp/bun-dl-*
Rimuovi anche gli hook malevoli da .claude/settings.json e .vscode/tasks.json, i file setup.mjs/Math_Symbol.js/math_init.js, e svuota le cache (~/.npm/_cacache, pnpm, yarn). Esegui di nuovo node scan.mjs host finché non torna pulito.
4. RUOTA — solo quando ogni host colpito è stato pulito e verificato (un singolo watcher attivo è sufficiente a far scattare il grilletto). Revoca prima il token npm (ferma la propagazione del worm); poi GitHub (PAT, deploy key), AWS/GCP/Azure, Vault, Kubernetes, i segreti CI — e qualsiasi segreto presente su disco, perché c'è stata una scansione con regex.
5. AUDIT — il worm agisce a tuo nome: cerca nelle tue organizzazioni repository descritti come Shai-Hulud: Here We Go Again, versioni npm pubblicate inaspettatamente dal 08-04 (deprecale e notifica i consumatori), e l'uso delle credenziali in CloudTrail/Audit Logs all'interno della finestra di started_at.
Dopodiché: elimina node_modules, reinstalla da un lockfile pulito con --ignore-scripts. Runner CI e host con esecuzione confermata: ricostruisci da zero, sempre — è stato eseguito codice arbitrario e l'elenco degli artefatti noti non è una garanzia di completezza.
Istituzioni regolamentate (BR): una compromissione confermata con accesso alle credenziali può far scattare obblighi di segnalazione (Res. CMN 4.893/2021, Res. BCB 85/2021; LGPD art. 48 se sono coinvolti dati personali). Documenta la timeline in UTC —
started_at, rilevazione, contenimento, eradicazione, rotazione — e conferma le scadenze con legal/compliance.
L'incidente è attivo e l'elenco cresce. Per scaricare il manifest più recente:
node scan.mjs --update # the only operation that touches the network
--update scarica iocs.json da questo repository (securest8/npm-incident-response), mai da terze parti — Securest8 è il gate di curation. Ottieni ciò che è stato pubblicato qui per ultimo.
L'elenco dei pacchetti proviene dal feed pubblico Wiz; gli hash, i domini C2, gli IOC di persistenza e le versioni sicure sono statici e curati in tools/gen-iocs.mjs. Uno snapshot del CSV di Wiz è conservato in tools/keyv-packages.csv per riproducibilità ed esecuzioni offline.
node tools/gen-iocs.mjs # fetch the latest Wiz CSV, regenerate iocs.json + refresh the snapshot
node tools/gen-iocs.mjs --offline # regenerate from the committed snapshot, no network
node tools/gen-iocs.mjs --allow-shrink # allow a package count lower than the snapshot (guarded by default)
Il generatore è idempotente: mantiene il manifest_version esistente e non riscrive il file quando non è cambiato nulla di sostanziale, e rifiuta di scrivere un manifest vuoto o ridotto (proteggendosi da un feed a monte troncato o modificato).
Automazione: .github/workflows/update-iocs.yml esegue il generatore quotidianamente (e su richiesta) e committa su main solo quando gli IOC cambiano davvero — così --update degli utenti segue il feed Wiz entro circa un giorno, con l'intera cronologia verificabile nei commit. Trasformalo in uno step a pull request (come indicato nel workflow) quando l'incidente si raffredda e preferisci un merge manuale.
node test/run-tests.mjs # 15 assertions against fixtures/demo-repo
fixtures/demo-repo è un repository di test con IOC inerti (nomi e versioni reali, contenuto fittizio — nessun malware). Quando si scansiona il repository dello scanner stesso, la cartella fixtures/ viene saltata automaticamente (con un avviso nell'output); i test la scansionano passando esplicitamente il percorso.
Uno strumento per un singolo incidente, costruito per un triage rapido durante la finestra attiva dell'attacco — non un sostituto di Socket, Snyk o simili. Ricerca e IOC: Socket.dev, Wiz Research (CSV pubblico), Kodem Security.
Mantenuto da Securest8. Licenza MIT.
| Livello | Significato | Azione |
|---|
COMPROMISED | Versione malevola installata in node_modules, payload confermato dall'hash, o trovato un impianto di persistenza | Tratta l'host come compromesso; segui l'ordine di risposta — pulisci l'impianto prima di ruotare le credenziali |
EXPOSED | Versione malevola fissata in un lockfile, nessuna prova di esecuzione | Fissa una versione sicura, elimina node_modules, reinstalla con --ignore-scripts |
AT_RISK | Intervallo (^/~) in package.json che ammette una versione malevola | Fissa una versione esatta o bloccala al proxy del registry |
SUSPECT | Euristica (nome di file del worm con hash non corrispondente, script di lifecycle sospetto) | Ispeziona manualmente — potrebbe essere una nuova variante o un falso positivo |
INFO | Vettore presente ma nessun IOC (es. un task folderOpen generico) | Rivedi |