131% flere rapporterte CVE-er per dag i 2026 enn i fjor.
← Alle sårbarheter

Shai-Hulud: npm-ormen, og slik sjekker du om byggene dine er berørt

Shai-Hulud er en selvspredende orm som infiserer npm-pakker, et angrep på programvareleverandørkjeden som verver hvert offer til å spre det videre. Den kjører når en pakke installeres, stjeler npm-tokens, GitHub-tokens og skynøkler fra maskinen og bruker npm-tokenet til å publisere infiserte versjoner av offerets andre pakker. Siden den første bølgen i september 2025 har den kommet tilbake gang på gang, og i mai 2026 publiserte opphavsmennene kildekoden, så hver bølge siden da kan komme fra en annen gruppe. Hvis noen av byggene dine installerte en infisert versjon, behandler du den jobben som kompromittert og roterer hver hemmelighet den kunne lese.

Mesteparten av skaden skjer i CI. I bølgen i november 2025 fant Wiz at bare 23 % av infeksjonene var på utviklermaskiner, resten hovedsakelig i CI/CD-pipelines, med GitHub Actions som den vanligste. Et bygg installerer hundrevis av pakker, har hemmelighetene som trengs for å publisere og rulle ut, og kjører hvert installasjonsskript uten at noen følger med. Så når en ny bølge blir kjent, stiller hvert team det samme spørsmålet: Installerte noen av byggene våre en infisert versjon?

Hva som skjedde

NårBølgeHva som var annerledes
September 2025Shai-HuludEt postinstall-skript kjørte en bundle.js på 3,6 MB, brukte TruffleHog til å finne hemmeligheter og publiserte opptil 20 av vedlikeholderens pakker på nytt. Mer enn 500 pakker ble rammet, blant dem @ctrl/tinycolor og 18 @crowdstrike-pakker. CISA publiserte et varsel 23. september.
November 2025Sha1-Hulud: The Second ComingFlyttet til preinstall, installerte Bun-kjøretiden for å kjøre en nyttelast på 10 MB, la bakdører i opptil 100 pakker per vedlikeholder og registrerte infiserte maskiner som selvhostede GitHub-runnere. Pakker fra Zapier, ENS, PostHog og Postman ble rammet, og Wiz talte mer enn 25 000 repositorier med lekkede hemmeligheter.
Desember 2025«3.0»-testEn modifisert versjon i én enkelt pakke, uten nevneverdig spredning.
Mai 2026TanStack84 ondsinnede versjoner av 42 @tanstack-pakker. Angriperen stjal aldri et npm-token: En pull_request_target-workflow, en forgiftet Actions-cache og et OIDC-token lest fra runnerens minne gjorde det mulig å publisere gjennom TanStacks egen trusted publishing. Spores som CVE-2026-45321, lagt til i CISAs KEV-katalog 27. mai.
Mai 2026AntV639 versjoner av 323 pakker. På Linux-runnere i GitHub Actions leste nyttelasten hemmeligheter fra minnet til runner-prosessen og omgikk dermed loggmaskeringen. GitHub ugyldiggjorde 61 274 npm-skrivetokens.
Juni 2026Miasma32 @redhat-cloud-services-pakker, publisert gjennom injiserte workflows med gyldig provenance. Hver infeksjon bar en unikt kryptert nyttelast, så hash-basert deteksjon sviktet.
August 2026CHAINDROPPopulære cachepakker som keyv, cacheable og cache-manager, med hundrevis av berørte pakker totalt. Den ble værende gjennom konfigurasjonsfiler for editorer og AI-assistenter i repositoriet.
September 2026Gjenoppblomstring7. september kom nyttelasten tilbake i fire pakker som slapp gjennom npms nye skanning etter skadevare ved publisering.
Oktober 2026tensorlake8. oktober ble [email protected] publisert med ormen. OX Security så nye angripernøkler og antyder «en uavhengig trusselaktør eller en ny gruppe som bruker navnet Shai-Hulud».

Dette gjør ormen i en CI-jobb

Detaljene endrer seg mellom bølgene, men mønsteret består:

  1. Den kjører ved installasjon. Et preinstall- eller postinstall-skript kjører så snart npm install eller npm ci når den infiserte versjonen. Ingenting i koden din trenger å importere den.
  2. Den samler hemmeligheter. Miljøvariabler, .npmrc-tokens, skylegitimasjon fra metadatatjenesten og hemmelighetshåndterere, og alt TruffleHog finner på disk. Senere bølger leste minnet til GitHub Actions-runneren direkte, noe som gir hemmeligheter som aldri ble eksponert for steget, og som maskeringen ville ha skjult i loggene.
  3. Den får dem ut. Tidlige bølger opprettet offentlige GitHub-repositorier oppkalt etter ormen og pushet en workflow som sendte hver repositoriehemmelighet til webhook.site. Senere bølger brukte angripernes egne servere og meldingsnettverk.
  4. Den sprer seg og blir værende. Med et stjålet npm-token publiserer den infiserte versjoner av andre pakker. Med et GitHub-token legger den til workflows, registrerer selvhostede runnere eller planter konfigurasjonsfiler som kjører neste gang noen åpner repositoriet.
  5. Den oppfører seg annerledes i CI. Nyttelasten fra november 2025 sjekket etter GITHUB_ACTIONS, BUILDKITE, CIRCLE_SHA1 og lignende variabler, og i CI kjørte den synkront og gjorde seg ferdig før bygget gikk videre.

Hvem som er berørt

Alle med maskiner eller bygg som installerte en infisert versjon mens den lå i registeret: utvikleres bærbare maskiner, CI-jobber, Docker-bygg som kjører npm install, og selvhostede runnere. Du trenger ikke å være direkte avhengig av pakken; en transitiv avhengighet eller et verktøy som kjøres med npx, er nok. Hver bølge har sin egen liste over pakker og versjoner, og infiserte versjoner fjernes som regel i løpet av timer eller dager, så det som betyr noe, er om noe installerte dem i det tidsvinduet.

Slik fungerer det

Shai-Hulud er ikke en feil i npm. Det er ondsinnet kode publisert inne i ellers legitime pakker, som GitHubs sikkerhetsvarsler om skadevare for de infiserte pakkene klassifiserer som innebygd ondsinnet kode (CWE-506). Hver bølge kombinerer noen få teknikker:

  • Kjøring ved installasjon. npm kjører livssyklusskriptene til en pakke, som preinstall, postinstall og prepare, under installasjonen, med tillatelsene til brukeren eller CI-jobben. En infisert versjon trenger bare å ligge i avhengighetstreet; ingenting trenger å importere den.
  • Spredning med stjålne publiseringsrettigheter. Med et npm- eller GitHub-token tatt fra et offer publiserer ormen nye versjoner av offerets andre pakker med nyttelasten lagt til, så hver infiserte vedlikeholder infiserer flere pakker.
  • Publisering gjennom offerets egen utgivelsespipeline (TanStack, mai 2026). Ifølge GitHubs sikkerhetsvarsel koblet angriperen sammen en pull_request_target-workflow, en GitHub Actions-cache forgiftet på tvers av tillitsgrensen mellom fork og base-repositorium, og et OIDC-token lest fra runnerens minne, og publiserte gjennom TanStacks trusted publishing uten å endre publiseringsworkflowen. De infiserte versjonene oppga en valgfri avhengighet til en commit i en fork, der prepare-skriptet kjørte nyttelasten og deretter feilet med vilje, så npm forkastet avhengigheten og ikke etterlot spor i node_modules.
  • Gyldig provenance (Miasma, juni 2026). Injiserte workflows publiserte fra ofrenes egne repositorier, så de infiserte versjonene hadde gyldig provenance, og en unikt kryptert nyttelast per infeksjon slo ut hash-basert deteksjon.

Slik sjekker du om du er berørt

  1. Skaff listen over berørte versjoner for bølgen. Sikkerhetsleverandører publiserer dem i løpet av timer. Datadog har for eksempel et repositorium med kompromitteringsindikatorer med pakke- og versjonslister.
  2. Søk i hver lockfile, på hver gren som kjørte CI i perioden, ikke bare standardgrenen.
  3. Søk i selve byggene. Se i CI-logger etter setup_bun.js, bun_environment.js eller andre filnavn som nevnes i varselet, og etter installasjonsutdata som nevner de berørte versjonene. Hvis du har SBOM-er per bygg, søker du etter versjonene i dem.
  4. Sjekk GitHub-organisasjonen din for nye repositorier oppkalt etter ormen, workflow-filer du ikke har skrevet (for eksempel shai-hulud-workflow.yml, discussion.yaml eller formatter_*.yml) og selvhostede runnere du ikke har registrert.

Kommandoer du kan tilpasse. For å søke i lockfilene på hver fjerngren legger du de berørte versjonene for bølgen i affected.txt, én name@version per linje:

git fetch --all --quiet
for ref in $(git for-each-ref --format='%(refname:short)' refs/remotes); do
  while read -r pkg; do
    name=${pkg%@*}; version=${pkg##*@}
    git grep -n -A3 -F "$name" "$ref" -- '*package-lock.json' '*yarn.lock' '*pnpm-lock.yaml' \
      | grep -F "$version" | sed "s|^|$pkg  |"
  done < affected.txt
done

For å liste organisasjonens selvhostede runnere, nye repositorier som nevner ormen, og workflow-filene ormen er kjent for å opprette (erstatt ORG):

gh api --paginate orgs/ORG/actions/runners --jq '.runners[] | [.name, .os, .status] | @tsv'
gh repo list ORG --limit 1000 --json name,description,createdAt \
  --jq '.[] | select((.description // "") | test("hulud"; "i")) | [.name, .createdAt] | @tsv'
for repo in $(gh repo list ORG --limit 1000 --json name --jq '.[].name'); do
  gh api "repos/ORG/$repo/contents/.github/workflows" --jq '.[].name' 2>/dev/null \
    | grep -E '^(shai-hulud-workflow\.yml|discussion\.yaml|formatter_.*\.yml)$' \
    | sed "s|^|$repo  |"
done

Repositorier kan også ha egne runnere (gh api repos/ORG/REPO/actions/runners), og tidlige bølger opprettet repositorier i de personlige kontoene til utviklerne hvis tokens de stjal, så be utviklerne dine om å sjekke sine.

Derfor avgjør ikke lockfilen saken

Standardrådet er å søke etter de berørte versjonene i package-lock.json, yarn.lock eller pnpm-lock.yaml. Gjør det først, men en ren lockfile beviser ikke et rent bygg:

  • Pakker installert utenfor lockfilen. npx some-tool installerer en pakke som ikke er en prosjektavhengighet, i npm-cachen. npm install -g i et oppsettsteg, et verktøy installert inne i et byggeskript og en npm install i en Dockerfile går alle utenom prosjektets lockfile.
  • Lockfilen på grenen i dag er ikke lockfilen som kjørte. Et bygg for tre uker siden brukte det lockfilen sa da, på den grenen som utløste det. Pull requests med avhengighetsoppdateringer kjører CI mot nye versjoner før noen har gått gjennom dem.
  • Cacher gjenoppretter det som ble installert før. En gjenopprettet node_modules eller npm-cache kan inneholde en versjon dagens lockfile ikke lenger nevner.
  • npm ci installerer det lockfilen sier. Hvis den infiserte versjonen allerede var låst, installerer en streng installasjon den trofast.
  • En korrekt publisert versjon kan likevel være ondsinnet. TanStack og Miasma ble kompromittert gjennom sine egne utgivelsesworkflows, og Miasmas infiserte versjoner hadde gyldig provenance. Å sjekke at en versjon ble riktig publisert, forteller deg ikke at den er ren.

Det du faktisk trenger, er en registrering av hvilke pakkeversjoner hvert bygg hentet, på tvers av hvert repositorium og hver gren, for hele perioden en infisert versjon lå i registeret. Se er vi berørt? slik vurderer du en ny CVE på tvers av byggene dine.

Slik retter du det, og hva du gjør hvis du ikke kan patche ennå

  1. Hvis et bygg installerte en berørt versjon, behandler du jobben som kompromittert. Roter hver hemmelighet den kunne lese: npm-tokens, GitHub-tokens, skylegitimasjon og deploy-nøkler. Siden senere bølger publiserte stjålne hemmeligheter i andre ofres kontoer, bør du ikke anta at du er trygg fordi ingenting dukket opp i dine egne.
  2. Tøm cachene. Slett GitHub Actions-cacher og npm-cachen på utviklermaskiner og selvhostede runnere, så neste bygg installerer rene versjoner:
gh cache delete --all --repo ORG/REPO   # GitHub Actions caches, per repository
npm cache clean --force
rm -rf node_modules "$(pnpm store path)"   # pnpm; for Yarn: yarn cache clean
  1. Fest en ren versjon og fjern workflowene, repositoriene, runnerne og konfigurasjonsfilene ormen la til.

Bekreft oppryddingen

  1. Hver lockfile peker på rene versjoner. På hver gren viser npm ls <package> (eller pnpm why eller yarn why) versjonen som er installert; ingen kan stå på listen for bølgen.
  2. En ren installasjon inneholder ingen nyttelastfiler. Etter cachetømmingen over installerer du på nytt og ser etter nyttelastfilene som nevnes i sikkerhetsvarslene:
find node_modules -name bundle.js -size +3M \
  -o -name setup_bun.js -o -name bun_environment.js -o -name router_init.js
  1. Den gamle legitimasjonen virker ikke lenger. Tilbakekalte npm-tokens er borte fra npm token list, gamle GitHub-tokens og deploy-nøkler er slettet, og skynøkler er deaktivert, ikke bare erstattet.
  2. Cachene er borte. gh cache list viser ingen cache opprettet før oppryddingen i noe berørt repositorium.
  3. Ingenting ormen la til, er igjen: ingen repositorier, workflow-filer, selvhostede runnere eller konfigurasjonsfiler for editorer og AI-assistenter som du ikke har opprettet.
  4. Neste bygg er rent. Sjekk versjonene det faktisk installerte, fra loggen eller SBOM-en per bygg, ikke bare lockfilen.

Slik begrenser du skaden neste gang

  • Slå av installasjonsskript der du kan. npm v12, utgitt i juli 2026, deaktiverer installasjonsskript som standard og lar deg tillate dem per pakke. pnpm og Yarn har lignende kontroller.
  • Vent før du tar i bruk nye versjoner. De fleste infiserte versjoner blir funnet og fjernet i løpet av timer eller dager. En ventetid på avhengighetsoppdateringer, som både Dependabot og Renovate støtter, holder dem ute av byggene dine. GitHubs pakkebrannmur, i offentlig forhåndsversjon for GitHub Enterprise Cloud siden 20. oktober 2026, sjekker npm-installasjoner for ondsinnede pakker og tilbyr også ventetider.
  • Publiser med trusted publishing og kortlivede tokens. npm tilbakekalte alle classic tokens i desember 2025. Bruk OIDC-basert trusted publishing fra CI i stedet for langlivede tokens, og hold publiseringsworkflows atskilt fra bygge- og testworkflows.
  • Herd workflowene ormen misbruker. Unngå pull_request_target, fest actions til commit-SHA-er, og gi GITHUB_TOKEN bare tillatelsene hver jobb trenger. Se GitHub Actions-hemmeligheter: slik lekker de og slik låser du dem ned.
  • Begrens hvor bygg kan koble seg til. Et bygg trenger pakkeregistrene sine og noen få API-er. Kan det bare nå disse, feiler ormens kall til webhook.site eller dens egne servere. Se slik stopper en egress-policy eksfiltrering fra GitHub Actions. Dette stopper ikke eksfiltrering gjennom en vert bygget bruker legitimt, for eksempel GitHubs eget API, som noen bølger utnyttet.

Sjekk om du er kompromittert

Shai-Hulud er ondsinnet av design: Hver maskin eller jobb som installerte en infisert versjon, kjørte den. Indikatorene varierer fra bølge til bølge, så bruk sikkerhetsvarselet for bølgen du sjekker. Fra primærkilder:

BølgeIndikatorerKilde
September 2025Nyttelasten bundle.js, SHA-256 46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09; workflowen .github/workflows/shai-hulud-workflow.yml på en gren shai-hulud; et offentlig repositorium med navnet Shai-Hulud; tilkoblinger til webhook.siteStepSecurity, CISA
November 2025setup_bun.js, SHA-1 d1829b4708126dcc7bea7437c04d1f10eacd4a16; bun_environment.js, SHA-1 d60ec97eea19fffb4809bc35b91033b52490ca11 eller 3d7570d14d34b0ba137d502f042b27b0f37a59fa; repositorier med cloud.json, contents.json, environment.json, truffleSecrets.json og actionsSecrets.json; workflowene .github/workflows/discussion.yaml og formatter_*.yml; en selvhostet runner med navnet SHA1HULUDWiz
Mai 2026, TanStack"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" i optionalDependencies; nyttelasten router_init.js (rundt 2,3 MB) i roten av pakken; hjelpeskriptet tanstack_runner.js; tilkoblinger til filev2.getsession.org og seed1.getsession.org til seed3.getsession.org, og nedlastinger i andre trinn fra litter.catbox.moeGHSA-g7cv-rxg3-hmpx

Senere bølger endret nyttelasten for hver infeksjon, så en filhash som ikke stemmer, beviser ingenting; match først på pakkeversjoner.

Når du skal anta at du er kompromittert. Behandle hver vert og CI-jobb som installerte en berørt versjon, som kompromittert, selv om installasjonen feilet eller ikke etterlot noe i node_modules. GitHubs sikkerhetsvarsel om TanStack sier at ethvert utvikler- eller CI-miljø som installerte en berørt versjon, «bør anses som kompromittert.» Sjekk fra den første publiseringen av den infiserte versjonen, ikke fra dagen bølgen ble kjent.

Roter legitimasjon. Roter alt installasjonen kunne nå. I TanStack-bølgen betydde det legitimasjon for AWS, GCP, Kubernetes, Vault, GitHub, npm og SSH, ifølge postmortem-rapporten. Gå gjennom revisjonslogger i skyen for aktivitet fra de berørte vertene under og etter installasjonsvinduet.

Tøm cacher og artefakter. Søk i artefaktrepositorier og avhengighetsproxyer etter bufrede kopier av de berørte versjonene og slett dem, slik CISA anbefaler. Slett node_modules, npm-cachen og CI-cacher; i TanStack-bølgen kom angriperen inn via en forgiftet cache av pnpm-lageret, så sjekk Actions-cacher i tillegg til pakkecacher. Bygg og gi ut på nytt alt en kompromittert jobb har produsert.

Hva du skal si til kundene dine

Hvis du leverer programvare bygget med npm, kommer kundene til å spørre om produktet ditt bærer ormen, eller om utgivelsespipelinen din er kompromittert. Svar med hva du sjekket og hva du gjorde:

Vi har sjekket hvert bygg av [produkt] mellom [dato] og [dato] mot de publiserte listene over npm-pakkeversjoner infisert med Shai-Hulud. [Ingen bygg installerte en berørt versjon.] / [Bygg av [komponent] [dato] installerte [pakke@versjon]; vi har rotert legitimasjonen disse byggene hadde tilgang til, bygget [produkt] på nytt fra rene avhengigheter og gitt ut [versjon]. Utgivelse [x til y] er berørt; oppgrader til [versjon].] Vi oppdaterer dette varselet hvis nye bølger endrer bildet.

Hvis du fortsatt sjekker, si det, og si når du kommer til å svare. En første uttalelse og maler for hvert utfall finner du i slik svarer du kunder etter et leverandørkjedeangrep.

Slik hjelper CRACI

CRACI kjører GitHub Actions-jobbene dine på egne runnere (runs-on: craci) og registrerer hver pakke hver jobb faktisk henter fra npm og andre registre, inkludert pakker gjenopprettet fra cacher. Denne registreringen dekker blindsonene over: npx-verktøy, globale installasjoner og installasjoner inne i byggeskript dukker alle opp, fordi registreringen kommer fra det jobben lastet ned, ikke fra det en lockfile oppga. Når en ny bølge blir kjent, slår du opp de berørte versjonene i byggene CRACI har registrert, i stedet for å rekonstruere dem fra lockfiler og logger, og hver jobbs nettverksspor viser hva den koblet seg til. Bygghistorikken tar vare på hvert tidligere bygg med SBOM-en, så du ser hvilke bygg som inneholdt en infisert versjon og i hvilket tidsvindu du var eksponert, innenfor oppbevaringsperioden din.

CRACIs egress-policy begrenser hvor en jobb kan koble seg til, valideres før jobben starter og blokkerer ved feil, og varsler deg når en jobb prøver å nå en destinasjon policyen ikke tillater. Infiserte utgivelser med en publisert CVE, som TanStack-bølgen, flagges som enhver annen sårbarhet, og policy gates kan blokkere bygg som inneholder dem. CRACI gir ikke pakker en poengsum for ondsinnet atferd og flagger ikke en ny infisert versjon før et sikkerhetsvarsel finnes. Kombiner det med et verktøy for pakkeomdømme hvis du vil ha infiserte versjoner blokkert før installasjon.

Se pakkeregistreringen fra et av dine egne bygg

Andre hendelser: alle sårbarheter.

Kilder og oppdateringer

Primærkilder og original forskning:

Oppdateringer:

  • 2026-11-11: La til EPSS, CWE-klassen og krediteringen i boksen, sjekker du kan kopiere og lime inn, og avsnitt om hvordan ormen fungerer, hvordan du bekrefter oppryddingen og hvordan du sjekker om du er kompromittert, med indikatorer per bølge.
  • 2026-11-11: Flyttet fra bloggen til sårbarhetsseksjonen. La til tensorlake-bølgen 8. oktober 2026, datoen for gjenoppblomstringen i september 2026, CVE-2026-45321 og KEV-oppføringen for TanStack-bølgen, og GitHubs pakkebrannmur.
  • 2026-09-25: Først publisert på CRACI-bloggen.

Ofte stilte spørsmål

Er Shai-Hulud fortsatt aktiv?

Ja. Den har kommet tilbake i bølger siden september 2025, og opphavsmennene publiserte kildekoden i mai 2026, så senere bølger kan komme fra andre grupper. Den siste bølgen som omtales her, rammet pakken tensorlake 8. oktober 2026.

Har Shai-Hulud en CVE?

Ormen som helhet har ingen. GitHub publiserer sikkerhetsvarsler om skadevare for enkeltpakker som er infisert, og TanStack-bølgen i mai 2026 har CVE-2026-45321, som CISA la til i katalogen Known Exploited Vulnerabilities 27. mai 2026.

Betyr en ren lockfile at vi ikke ble rammet?

Nei. Pakker installert med npx, globale installasjoner, installasjoner i byggeskript og Dockerfiles, eldre lockfiler på andre grener og gjenopprettede cacher kan alle hente inn en infisert versjon som dagens lockfile ikke viser. Du trenger en registrering av hva hvert bygg faktisk hentet.

Hvordan sjekker jeg om jeg er rammet?

Skaff de berørte pakkeversjonene for bølgen, søk i hver lockfile på hver gren som kjørte CI i perioden, søk i CI-logger og SBOM-er per bygg etter versjonene og filnavnene til nyttelasten, og sjekk GitHub-organisasjonen din for repositorier, workflows og selvhostede runnere du ikke har opprettet.

Hva gjør jeg hvis et bygg installerte en infisert versjon?

Behandle jobben som kompromittert. Roter hver hemmelighet den kunne lese, inkludert npm-tokens, GitHub-tokens, skylegitimasjon og deploy-nøkler, slett GitHub Actions-cacher og npm-cacher, og se etter workflows, repositorier og runnere som ormen har lagt til.

Hvordan vet jeg om ormen kjørte i byggene våre?

Søk i byggelogger, lockfiler og installerte pakker etter indikatorene for bølgen: nyttelastfiler som bundle.js, setup_bun.js, bun_environment.js eller router_init.js, repositorier og workflows du ikke har opprettet, en selvhostet runner med navnet SHA1HULUD og tilkoblinger til webhook.site eller getsession.org. Hver jobb som installerte en berørt versjon, kjørte nyttelasten, så behandle den som kompromittert selv om du ikke finner noe annet.