Viermele npm din keyv și cacheable: cum verifici în două minute dacă te-a atins
Pe 4 august 2026, un cont de mainteiner compromis a otrăvit keyv, cacheable și peste 400 de pachete npm. Comenzile concrete de verificare și configurația care oprește această clasă de atac.
Pe 4 august 2026, contul de mainteiner care administrează familiile de pachete npm „keyv" și „cacheable" a fost compromis. Atacatorul a publicat versiuni otrăvite ale cel puțin zece pachete, cu un hook „preinstall" care fură credențiale și republică automat alte pachete la care ajunge tokenul furat. În câteva ore, codul se propagase la peste 400 de pachete npm distincte, sărind de la o organizație la alta la fiecare două-șapte minute. Singur pachetul keyv are aproximativ 127 de milioane de descărcări săptămânale.
Ce face, concret, codul malițios
- Un script „preinstall" (setup.mjs) rulează automat la instalare, înainte ca tu să apuci să te uiți la ceva. Nu trebuie să imporți pachetul, nu trebuie să rulezi aplicația — e suficient npm install.
- Descarcă un runtime Bun independent și execută o a doua etapă ofuscată, ca să nu depindă de ce ai tu instalat.
- Colectează credențiale din mediu: tokenuri npm, credențiale GitHub, chei cloud, tokenuri de CI, chei private. Interoghează inclusiv serviciile de metadate ale instanțelor (169.254.169.254), de unde se pot obține credențiale temporare de cloud.
- Cu tokenul npm furat, republică versiuni trojanizate ale altor pachete la care are acces — de aici caracterul de vierme și saltul între organizații.
- Plantează persistență: un script gh-token-monitor, un LaunchAgent pe macOS sau un serviciu systemd pe Linux, plus hook-uri în fișierele de configurare ale uneltelor de dezvoltare.
Analiza atribuie codul familiei Shai-Hulud, într-o variantă redusă. Payload-ul are și un „dead-man's switch": interoghează API-ul GitHub la fiecare 60 de secunde și, când pierde accesul la tokenurile furate, își șterge starea și iese.
Versiunile compromise
- keyv@6.0.0 și pachetele @keyv/* la versiunea 6.0.0
- cacheable@2.5.1, @cacheable/net@2.1.1, @cacheable/node-cache@3.1.2, @cacheable/memory@2.2.1, @cacheable/utils@2.5.1
- flat-cache@6.1.24
- file-entry-cache@11.1.7
- cacheable-request@13.0.20
- cache-manager@7.2.10
Reține că lista de mai sus este punctul de plecare, nu totalul: viermele a trojanizat sute de alte pachete, din organizații care nu au nicio legătură cu keyv. Verificarea pe nume de pachet nu este suficientă — de aceea contează și verificările de mai jos, care se uită la ce s-a descărcat efectiv pe mașină.
Verificarea 1 — ai familia asta în arborele de dependențe?
Aproape sigur da, chiar dacă nu ai instalat-o niciodată direct: keyv ajunge în majoritatea proiectelor JavaScript prin ESLint. Rulează în rădăcina proiectului:
npm ls keyv cacheable cacheable-request flat-cache file-entry-cache cache-manager --allPrezența lor nu înseamnă nimic în sine. Contează versiunea, iar asta o verifici la pasul următor.
Verificarea 2 — ești pe o versiune compromisă?
Comanda de mai jos citește package-lock.json și compară exact cu lista de versiuni otrăvite. Rulează-o în rădăcina fiecărui proiect care are lockfile propriu — inclusiv subproiecte precum funcțiile serverless, care au al lor separat:
node -e '
const lock = require("./package-lock.json");
const BAD = { "keyv":"6.0.0", "cacheable":"2.5.1", "cacheable-request":"13.0.20",
"flat-cache":"6.1.24", "file-entry-cache":"11.1.7", "cache-manager":"7.2.10",
"@cacheable/net":"2.1.1", "@cacheable/node-cache":"3.1.2",
"@cacheable/memory":"2.2.1", "@cacheable/utils":"2.5.1" };
let hit = false;
for (const [p, meta] of Object.entries(lock.packages || {})) {
const name = p.split("node_modules/").pop();
if (BAD[name] === meta.version || (name.startsWith("@keyv/") && meta.version === "6.0.0")) {
console.log("COMPROMIS:", name + "@" + meta.version); hit = true;
}
}
console.log(hit ? ">>> ACTIONEAZA ACUM" : ">>> curat");'Verifică apoi și dacă un install proaspăt, fără lockfile, ar putea aduce versiunile otrăvite. Dacă intervalele semver din arbore sunt de tipul „^8.0.0" iar versiunea compromisă este 11.1.7, nu au cum să ajungă la ea — asta e o concluzie structurală, mai puternică decât un instantaneu al lockfile-ului.
Verificarea 3 — a descărcat vreodată mașina asta un pachet otrăvit?
Aceasta este verificarea cea mai valoroasă și cea mai des omisă. Se uită în cache-ul npm global, deci acoperă toate proiectele de pe mașină, inclusiv cele pe care le-ai uitat sau le-ai șters. Dacă nu returnează nimic, niciun tarball compromis nu a atins mașina — indiferent ce scrie în lockfile-uri:
grep -rl "keyv/-/keyv-6\.\|flat-cache/-/flat-cache-6\.\|\
file-entry-cache/-/file-entry-cache-11\.\|\
cacheable-request/-/cacheable-request-13\.\|\
cache-manager/-/cache-manager-7\.2\.10" ~/.npm/_cacache/index-v5 2>/dev/nullVerifică și când ai instalat ultima oară, cu „ls -ld node_modules". Dacă data este anterioară zilei de 4 august 2026, niciun install nu a rulat în fereastra de expunere și condiția de declanșare nu s-a îndeplinit.
Verificarea 4 — semne de persistență pe stația de lucru
Dacă verificările anterioare au ieșit curate, aceasta va ieși la fel — dar merită rulată, pentru că este singura care detectează o infecție venită pe alt drum decât proiectul pe care îl inspectezi:
# macOS și Linux
ls -la ~/.local/bin/gh-token-monitor.sh ~/.config/gh-token-monitor 2>/dev/null
ls -la ~/Library/LaunchAgents/com.user.gh-token-monitor.plist 2>/dev/null
systemctl --user list-units "gh-token-monitor*" 2>/dev/null
# hook-uri plantate în uneltele de dezvoltare, rulează în rădăcina proiectului
grep -l "gh-token-monitor" .claude/settings.json .vscode/tasks.json 2>/dev/null
# runtime-ul descărcat de payload
ls -la ~/.bun 2>/dev/null; command -v bunDacă ai găsit ceva
Presupune că tot ce era accesibil din acel mediu a fost exfiltrat, și tratează rotația ca pe o urgență, în ordinea asta: tokenuri npm, credențiale GitHub și GitHub CLI, chei de acces cloud, tokenuri de CI/CD și secrete din pipeline, chei SSH și chei private. Rotește-le de pe o mașină despre care știi că este curată, nu de pe cea suspectă. Abia apoi șterge node_modules, curăță cache-ul npm și reinstalează din lockfile. Verifică în paralel istoricul de publicare al pachetelor tale pe npm: dacă tokenul tău a fost folosit, semnul îl vei vedea acolo, ca versiune pe care nu ai publicat-o tu.
Protecția reală: patru setări care opresc clasa asta de atac
Verificările de mai sus îți spun dacă ai fost atins de acest incident. Următoarele patru setări reduc drastic impactul următorului — și va exista un următor, pentru că modelul de amenințare nu s-a schimbat.
1. Blochează scripturile de instalare. Este cea mai eficientă măsură per unitate de efort: întregul atac depinde de un hook „preinstall" care rulează automat. Fără el, pachetul otrăvit ajunge pe disc, dar nu execută nimic.
npm config set ignore-scripts true
# în .npmrc, versionat în depozit:
ignore-scripts=trueCâteva pachete legitime chiar au nevoie de scripturi ca să compileze binare native (esbuild și sharp sunt exemplele clasice). Pentru ele rulezi „npm rebuild nume-pachet" deliberat, o dată, în loc să lași ușa deschisă pentru toate.
2. Impune o perioadă de decantare pentru versiuni noi. Cele mai multe versiuni malițioase sunt detectate și retrase în câteva ore — în acest incident, în medie la cinci minute și optsprezece secunde de la publicare. O întârziere de 24 de ore la instalarea versiunilor proaspete elimină practic toată această categorie de atac, fără niciun cost real pentru tine.
# pnpm 10.16+ — implicit 1440 de minute (24h) începând cu pnpm 11
pnpm config set minimumReleaseAge 1440 --location=user
# npm — instalează doar versiuni publicate înainte de o dată dată
npm install --before=2026-08-013. Folosește „npm ci", niciodată „npm install", în CI. „npm ci" instalează exact ce scrie în lockfile și eșuează dacă lockfile-ul nu corespunde cu package.json. „npm install" poate ridica versiuni în interiorul intervalelor semver, ceea ce înseamnă că un pipeline poate trage singur o versiune otrăvită publicată acum zece minute, fără ca nimeni să fi modificat ceva.
4. Tratează credențialele din CI ca fiind deja compromise. Folosește identitate federată cu durată scurtă (OIDC) în loc de chei de acces permanente, limitează permisiunile la strictul necesar și nu ține tokenuri npm cu drept de publicare în medii care rulează cod din dependențe. Dacă tot ce poate fura un „preinstall" este un token valabil cincisprezece minute și limitat la un singur tabel, atacul se oprește acolo.
Ce nu ajută, contrar așteptărilor
- Provenance-ul și semnăturile. În acest incident au fost valide, pentru că pipeline-ul legitim a publicat codul atacatorului. Provenance-ul dovedește de unde vine un artefact, nu că este curat.
- Un audit vizual al pachetelor populare. Pachetul era popular, întreținut și legitim până cu o versiune înainte. Popularitatea nu este un control de securitate.
- Antivirusul de pe stația de lucru. Payload-ul rulează prin Node și printr-un runtime descărcat legitim, cu drepturile tale de utilizator.
- Sperând că tu, personal, vei observa. Fereastra a fost de minute, iar declanșarea este automată la instalare. Aici te salvează configurația, nu vigilența.
Legătura cu awareness-ul
Atacurile pe lanțul de aprovizionare software par o problemă strict tehnică, dar au aceeași structură ca phishingul: cineva de încredere trimite ceva care pare normal, iar victima execută automat, fără verificare. Diferența este că aici „victima" este pipeline-ul, iar „clicul" este un npm install. Instruirea care contează pentru echipele de dezvoltare nu este să recunoască pachete malițioase — nu se poate — ci să știe că un install este o execuție de cod, să nu ruleze instalări cu credențiale privilegiate în mediu și să știe exact pe cine anunță în primele minute. Restul se rezolvă din configurație, o singură dată, și protejează apoi automat.
Surse
- Wiz — keyv and cacheable npm Package Hijacked in Supply Chain Attack
- Socket — Popular npm packages in the keyv and cacheable namespaces compromised
- Snyk — Inside the keyv npm Supply Chain Compromise
- The Hacker News — Keyv-Linked npm Worm Poisons Hundreds of Packages
- Aikido — Keyv and friends compromised in npm supply chain attack
- pnpm — Mitigating supply chain attacks (minimumReleaseAge)