Blog
Alerte de securitate4 august 202610 min de citit

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.

Detaliul care contează cel mai mult: versiunile otrăvite au avut semnături de provenance GitHub Actions perfect valide. Atacatorul a împins codul direct în ramura principală a fiecărui depozit și a lăsat pipeline-ul legitim să publice. Dacă strategia ta de apărare era „verific provenance-ul", acest atac a trecut prin ea fără să o atingă.

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 --all

Prezenț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:

Verificare pe lockfile
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:

Cache-ul npm global (fără rezultate = curat)
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/null

Verifică ș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:

Indicatori de compromitere (fără rezultate = curat)
# 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 bun

Dacă 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.

Global, sau în .npmrc-ul proiectului
npm config set ignore-scripts true

# în .npmrc, versionat în depozit:
ignore-scripts=true

Câ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.

Decantare (cooldown) pe manageri de pachete
# 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-01

3. 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.

#npm#lanț de aprovizionare#keyv#cacheable#Shai-Hulud#DevSecOps#provenance#CI/CD