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år | Bølge | Hva som var annerledes |
|---|---|---|
| September 2025 | Shai-Hulud | Et 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 2025 | Sha1-Hulud: The Second Coming | Flyttet 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»-test | En modifisert versjon i én enkelt pakke, uten nevneverdig spredning. |
| Mai 2026 | TanStack | 84 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 2026 | AntV | 639 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 2026 | Miasma | 32 @redhat-cloud-services-pakker, publisert gjennom injiserte workflows med gyldig provenance. Hver infeksjon bar en unikt kryptert nyttelast, så hash-basert deteksjon sviktet. |
| August 2026 | CHAINDROP | Populæ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 2026 | Gjenoppblomstring | 7. september kom nyttelasten tilbake i fire pakker som slapp gjennom npms nye skanning etter skadevare ved publisering. |
| Oktober 2026 | tensorlake | 8. 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:
- Den kjører ved installasjon. Et
preinstall- ellerpostinstall-skript kjører så snartnpm installellernpm cinår den infiserte versjonen. Ingenting i koden din trenger å importere den. - 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. - 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. - 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.
- Den oppfører seg annerledes i CI. Nyttelasten fra november 2025 sjekket etter
GITHUB_ACTIONS,BUILDKITE,CIRCLE_SHA1og 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,postinstallogprepare, 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, derprepare-skriptet kjørte nyttelasten og deretter feilet med vilje, så npm forkastet avhengigheten og ikke etterlot spor inode_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
- 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.
- Søk i hver lockfile, på hver gren som kjørte CI i perioden, ikke bare standardgrenen.
- Søk i selve byggene. Se i CI-logger etter
setup_bun.js,bun_environment.jseller 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. - Sjekk GitHub-organisasjonen din for nye repositorier oppkalt etter ormen, workflow-filer du ikke har skrevet (for eksempel
shai-hulud-workflow.yml,discussion.yamlellerformatter_*.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-toolinstallerer en pakke som ikke er en prosjektavhengighet, i npm-cachen.npm install -gi et oppsettsteg, et verktøy installert inne i et byggeskript og ennpm installi 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_moduleseller npm-cache kan inneholde en versjon dagens lockfile ikke lenger nevner. npm ciinstallerer 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å
- 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.
- 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
- Fest en ren versjon og fjern workflowene, repositoriene, runnerne og konfigurasjonsfilene ormen la til.
Bekreft oppryddingen
- Hver lockfile peker på rene versjoner. På hver gren viser
npm ls <package>(ellerpnpm whyelleryarn why) versjonen som er installert; ingen kan stå på listen for bølgen. - 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
- 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. - Cachene er borte.
gh cache listviser ingen cache opprettet før oppryddingen i noe berørt repositorium. - Ingenting ormen la til, er igjen: ingen repositorier, workflow-filer, selvhostede runnere eller konfigurasjonsfiler for editorer og AI-assistenter som du ikke har opprettet.
- 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 giGITHUB_TOKENbare 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.siteeller 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ølge | Indikatorer | Kilde |
|---|---|---|
| September 2025 | Nyttelasten 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.site | StepSecurity, CISA |
| November 2025 | setup_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 SHA1HULUD | Wiz |
| 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.moe | GHSA-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:
- CISA: varsel fra 23. september 2025, KEV-katalogen
- GitHub: GHSA-g7cv-rxg3-hmpx (CVE-2026-45321), planen vår for en sikrere npm-leverandørkjede, npms classic tokens tilbakekalt
- TanStacks postmortem
- Wiz om bølgen i november 2025 og etterspillet
- Microsoft om AntV og Microsofts veiledning for deteksjon
- Datadog og Elastic om CHAINDROP
- StepSecurity om bølgen i september 2025
- FIRST: EPSS-poeng for CVE-2026-45321
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.