131% flere rapporterede CVE'er pr. dag i 2026 end sidste år.
← Alle sårbarheder

Shai-Hulud: npm-ormen, og sådan tjekker du, om dine builds er berørt

Shai-Hulud er en selvspredende orm, der inficerer npm-pakker, et angreb på softwareforsyningskæden, der hverver hvert offer til at sprede det videre. Den kører, når en pakke installeres, stjæler npm-tokens, GitHub-tokens og cloud-nøgler fra maskinen og bruger npm-tokenet til at udgive inficerede versioner af ofrets andre pakker. Siden den første bølge i september 2025 er den vendt tilbage gentagne gange, og i maj 2026 offentliggjorde ophavsmændene kildekoden, så hver bølge siden da kan stamme fra en anden gruppe. Hvis nogen af dine builds installerede en inficeret version, så behandl det job som kompromitteret, og rotér hver secret, det kunne læse.

Det meste af skaden sker i CI. I bølgen i november 2025 fandt Wiz, at kun 23 % af infektionerne var på udvikleres maskiner, resten hovedsageligt i CI/CD-pipelines, oftest GitHub Actions. Et build installerer hundredvis af pakker, indeholder de secrets, der skal til for at udgive og deploye, og kører hvert install-script, uden at nogen holder øje. Så når en ny bølge bliver annonceret, stiller hvert team det samme spørgsmål: Installerede nogen af vores builds en inficeret version?

Hvad der skete

HvornårBølgeHvad der var anderledes
September 2025Shai-HuludEt postinstall-script kørte en bundle.js på 3,6 MB, brugte TruffleHog til at finde secrets og genudgav op til 20 af maintainerens pakker. Mere end 500 pakker blev ramt, inklusive @ctrl/tinycolor og 18 @crowdstrike-pakker. CISA udsendte en advarsel den 23. september.
November 2025Sha1-Hulud: The Second ComingFlyttede til preinstall, installerede Bun-runtimen for at køre en payload på 10 MB, indsatte bagdøre i op til 100 pakker pr. maintainer og registrerede inficerede maskiner som selvhostede GitHub-runnere. Pakker fra Zapier, ENS, PostHog og Postman blev ramt, og Wiz talte mere end 25.000 repositories med lækkede secrets.
December 2025"3.0"-testEn ændret version i en enkelt pakke, uden nævneværdig spredning.
Maj 2026TanStack84 ondsindede versioner af 42 @tanstack-pakker. Angriberen stjal aldrig et npm-token: Et pull_request_target-workflow, en forgiftet Actions-cache og et OIDC-token læst fra runnerens hukommelse gjorde det muligt at udgive gennem TanStacks egen trusted publishing. Registreret som CVE-2026-45321 og føjet til CISA's KEV-katalog den 27. maj.
Maj 2026AntV639 versioner af 323 pakker. På GitHub Actions' Linux-runnere læste payloaden secrets fra runner-processens hukommelse og omgik dermed logmaskering. GitHub ugyldiggjorde 61.274 npm-skrivetokens.
Juni 2026Miasma32 @redhat-cloud-services-pakker, udgivet via injicerede workflows med gyldig provenance. Hver infektion bar en unikt krypteret payload, så hash-baseret detektion fejlede.
August 2026CHAINDROPPopulære caching-pakker som keyv, cacheable og cache-manager, med hundredvis af ramte pakker i alt. Den bevarede fodfæstet gennem konfigurationsfiler til editorer og AI-assistenter i repositoriet.
September 2026GenkomstDen 7. september vendte payloaden tilbage i fire pakker, der slap forbi npm's nye malware-scanning ved udgivelse.
Oktober 2026tensorlakeDen 8. oktober blev [email protected] udgivet med ormen. OX Security så nye angribernøgler og peger på "en uafhængig trusselsaktør eller en ny gruppe, der bruger navnet Shai-Hulud".

Hvad ormen gør i et CI-job

Detaljerne ændrer sig mellem bølgerne, men mønsteret holder:

  1. Den kører ved installation. Et preinstall- eller postinstall-script kører, så snart npm install eller npm ci når den inficerede version. Intet i din kode behøver at importere den.
  2. Den indsamler secrets. Miljøvariabler, .npmrc-tokens, cloud-legitimationsoplysninger fra metadatatjenesten og secret managers samt alt, hvad TruffleHog finder på disken. Senere bølger læste GitHub Actions-runnerens hukommelse direkte, hvilket giver secrets, som aldrig blev eksponeret for trinnet, og som maskering ville have skjult i logs.
  3. Den får dem ud. Tidlige bølger oprettede offentlige GitHub-repositories opkaldt efter ormen og pushede et workflow, der sendte hver repository-secret til webhook.site. Senere bølger brugte angribernes servere og beskednetværk.
  4. Den spreder sig og bliver. Med et stjålet npm-token udgiver den inficerede versioner af andre pakker. Med et GitHub-token tilføjer den workflows, registrerer selvhostede runnere eller planter konfigurationsfiler, der kører, næste gang nogen åbner repositoriet.
  5. Den opfører sig anderledes i CI. Payloaden fra november 2025 tjekkede efter GITHUB_ACTIONS, BUILDKITE, CIRCLE_SHA1 og lignende variabler, og i CI kørte den synkront og blev færdig med sit arbejde, før buildet fortsatte.

Hvem er berørt

Alle, hvis maskiner eller builds installerede en inficeret version, mens den lå i registret: udvikleres bærbare, CI-jobs, Docker-builds, der kører npm install, og selvhostede runnere. Du behøver ikke at være direkte afhængig af pakken; en transitiv afhængighed eller et værktøj, der køres med npx, er nok. Hver bølge har sin egen liste over pakker og versioner, og inficerede versioner bliver som regel fjernet inden for timer eller dage, så det afgørende er, om noget installerede dem i det tidsvindue.

Sådan virker det

Shai-Hulud er ikke en fejl i npm. Det er ondsindet kode, der udgives inde i ellers legitime pakker, og som GitHubs malware-advisories for de inficerede pakker klassificerer som indlejret ondsindet kode (CWE-506). Hver bølge kombinerer nogle få teknikker:

  • Kørsel ved installation. npm kører en pakkes lifecycle-scripts, såsom preinstall, postinstall og prepare, under installationen med brugerens eller CI-jobbets rettigheder. En inficeret version skal blot være i afhængighedstræet; intet behøver at importere den.
  • Spredning med stjålne udgivelsesrettigheder. Med et npm- eller GitHub-token taget fra et offer udgiver ormen nye versioner af ofrets andre pakker med payloaden tilføjet, så hver inficeret maintainer inficerer flere pakker.
  • Udgivelse gennem ofrets egen release-pipeline (TanStack, maj 2026). Ifølge GitHubs advisory kædede angriberen et pull_request_target-workflow, en GitHub Actions-cache forgiftet på tværs af tillidsgrænsen mellem fork og base-repository og et OIDC-token læst fra runnerens hukommelse sammen og udgav gennem TanStacks trusted publishing uden at ændre publish-workflowet. De inficerede versioner erklærede en valgfri afhængighed af et commit i en fork, hvis prepare-script kørte payloaden og derefter fejlede med vilje, så npm kasserede afhængigheden og ikke efterlod spor i node_modules.
  • Gyldig provenance (Miasma, juni 2026). Injicerede workflows udgav fra ofrenes egne repositories, så de inficerede versioner havde gyldig provenance, og en unikt krypteret payload pr. infektion slog hash-baseret detektion ud.

Sådan tjekker du, om du er berørt

  1. Hent listen over berørte versioner for bølgen. Sikkerhedsleverandører offentliggør dem inden for få timer. Datadog vedligeholder for eksempel et repository med indicators of compromise med pakke- og versionslister.
  2. Søg i hver lockfile på hver branch, der kørte CI i perioden, ikke kun default-branchen.
  3. Søg i selve buildene. Kig i CI-logs efter setup_bun.js, bun_environment.js eller andre filnavne, der nævnes i advisoryen, og efter install-output, der nævner de berørte versioner. Hvis du gemmer SBOM'er pr. build, så søg efter versionerne i dem.
  4. Tjek din GitHub-organisation for nye repositories opkaldt efter ormen, workflow-filer, du ikke har skrevet (for eksempel shai-hulud-workflow.yml, discussion.yaml eller formatter_*.yml), og selvhostede runnere, du ikke har registreret.

Kommandoer, du kan tilpasse. For at søge i lockfiles på hver remote branch lægger du bølgens berørte versioner i affected.txt, én name@version pr. 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 at liste din organisations selvhostede runnere, nye repositories, der nævner ormen, og de workflow-filer, ormen vides at oprette (erstat 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

Repositories kan også have deres egne runnere (gh api repos/ORG/REPO/actions/runners), og tidlige bølger oprettede repositories på de personlige konti hos de udviklere, hvis tokens de stjal, så bed dine udviklere om at tjekke deres.

Derfor afgør lockfilen det ikke

Det almindelige råd er at søge efter de berørte versioner i package-lock.json, yarn.lock eller pnpm-lock.yaml. Gør det først, men en ren lockfile beviser ikke et rent build:

  • Pakker installeret uden om lockfilen. npx some-tool installerer en pakke, der ikke er en projektafhængighed, i npm-cachen. npm install -g i et opsætningstrin, et værktøj installeret i et build-script og en npm install i en Dockerfile går alle uden om projektets lockfile.
  • Lockfilen på branchen i dag er ikke den lockfile, der kørte. Et build for tre uger siden brugte det, lockfilen sagde dengang, på den branch, der udløste det. Pull requests med afhængighedsopdateringer kører CI mod nye versioner, før nogen har gennemgået dem.
  • Caches gendanner det, der blev installeret før. En gendannet node_modules eller npm-cache kan indeholde en version, den aktuelle lockfile ikke længere nævner.
  • npm ci installerer det, lockfilen siger. Hvis den inficerede version allerede var låst, installerer en streng installation den trofast.
  • En korrekt udgivet version kan stadig være ondsindet. TanStack og Miasma blev kompromitteret gennem deres egne release-workflows, og Miasmas inficerede versioner havde gyldig provenance. At tjekke, at en version er udgivet korrekt, fortæller dig ikke, at den er ren.

Det, du faktisk har brug for, er en registrering af, hvilke pakkeversioner hvert build hentede, på tværs af hvert repository og hver branch, i hele den periode, en inficeret version lå i registret. Se er vi berørt? sådan vurderer du en ny CVE på tværs af dine builds.

Sådan retter du det, og hvad du gør, hvis du ikke kan patche endnu

  1. Hvis et build installerede en berørt version, så behandl jobbet som kompromitteret. Rotér hver secret, det kunne læse: npm-tokens, GitHub-tokens, cloud-legitimationsoplysninger og deploy-nøgler. Fordi senere bølger offentliggjorde stjålne secrets på andre ofres konti, skal du ikke gå ud fra, at du er sikker, fordi intet dukkede op på din egen.
  2. Ryd cachene. Slet GitHub Actions-caches og npm-cachen på udvikleres maskiner og selvhostede runnere, så næste build installerer rene versioner:
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. Fastlås en ren version, og fjern de workflows, repositories, runnere og konfigurationsfiler, som ormen tilføjede.

Verificér oprydningen

  1. Hver lockfile peger på rene versioner. På hver branch viser npm ls <package> (eller pnpm why eller yarn why) den installerede version; ingen må stå på bølgens liste.
  2. En ren installation indeholder ingen payload-filer. Efter cache-rydningen ovenfor installerer du igen og kigger efter de payload-filer, der nævnes i advisories:
find node_modules -name bundle.js -size +3M \
  -o -name setup_bun.js -o -name bun_environment.js -o -name router_init.js
  1. De gamle legitimationsoplysninger virker ikke længere. Tilbagekaldte npm-tokens er væk fra npm token list, gamle GitHub-tokens og deploy-nøgler er slettet, og cloud-nøgler er deaktiveret, ikke kun erstattet.
  2. Cachene er væk. gh cache list viser ingen cache oprettet før oprydningen i noget berørt repository.
  3. Intet, ormen tilføjede, er tilbage: ingen repositories, workflow-filer, selvhostede runnere eller konfigurationsfiler til editorer og AI-assistenter, du ikke har oprettet.
  4. Det næste build er rent. Tjek de versioner, det faktisk installerede, ud fra dets log eller SBOM pr. build, ikke kun lockfilen.

Sådan mindsker du skaden næste gang

  • Slå install-scripts fra, hvor du kan. npm v12, der blev udgivet i juli 2026, slår som standard install-scripts fra og lader dig tillade dem pr. pakke. pnpm og Yarn har lignende kontroller.
  • Vent, før du tager nye versioner i brug. De fleste inficerede versioner bliver fundet og fjernet inden for timer eller dage. En cooldown på afhængighedsopdateringer, som både Dependabot og Renovate understøtter, holder dem ude af dine builds. GitHubs package firewall, der har været i public preview for GitHub Enterprise Cloud siden den 20. oktober 2026, tjekker npm-installationer for ondsindede pakker og tilbyder også cooldowns.
  • Udgiv med trusted publishing og kortlivede tokens. npm tilbagekaldte alle classic tokens i december 2025. Brug OIDC-baseret trusted publishing fra CI i stedet for langlivede tokens, og hold publish-workflows adskilt fra build- og test-workflows.
  • Hærd de workflows, ormen misbruger. Undgå pull_request_target, fastlås actions på commit-SHA'er, og giv GITHUB_TOKEN kun de rettigheder, hvert job har brug for. Se GitHub Actions-secrets: sådan lækker de, og sådan låser du dem ned.
  • Begræns, hvad builds kan oprette forbindelse til. Et build har brug for sine pakkeregistre og nogle få API'er. Hvis det kun kan nå dem, fejler ormens kald til webhook.site eller dens egne servere. Se hvordan en egress-politik stopper eksfiltrering fra GitHub Actions. Det stopper ikke eksfiltrering gennem en host, buildet bruger legitimt, for eksempel GitHubs eget API, som nogle bølger udnyttede.

Tjek for kompromittering

Shai-Hulud er ondsindet af design: Enhver maskine eller ethvert job, der installerede en inficeret version, kørte den. Indikatorerne er forskellige fra bølge til bølge, så brug advisoryen for den bølge, du tjekker. Fra primære kilder:

BølgeIndikatorerKilde
September 2025Payloaden bundle.js, SHA-256 46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09; workflowet .github/workflows/shai-hulud-workflow.yml på en branch shai-hulud; et offentligt repository ved navn Shai-Hulud; forbindelser til webhook.siteStepSecurity, CISA
November 2025setup_bun.js, SHA-1 d1829b4708126dcc7bea7437c04d1f10eacd4a16; bun_environment.js, SHA-1 d60ec97eea19fffb4809bc35b91033b52490ca11 eller 3d7570d14d34b0ba137d502f042b27b0f37a59fa; repositories med cloud.json, contents.json, environment.json, truffleSecrets.json og actionsSecrets.json; workflows .github/workflows/discussion.yaml og formatter_*.yml; en selvhostet runner ved navn SHA1HULUDWiz
Maj 2026, TanStack"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" i optionalDependencies; payloaden router_init.js (cirka 2,3 MB) i pakkens rod; hjælpescriptet tanstack_runner.js; forbindelser til filev2.getsession.org og seed1.getsession.org til seed3.getsession.org samt downloads i andet trin fra litter.catbox.moeGHSA-g7cv-rxg3-hmpx

Senere bølger ændrede payloaden for hver infektion, så en fil-hash, der ikke matcher, beviser intet; match først på pakkeversioner.

Hvornår du skal gå ud fra, at der er sket en kompromittering. Behandl hver host og hvert CI-job, der installerede en berørt version, som kompromitteret, også selvom installationen fejlede eller ikke efterlod noget i node_modules. GitHubs advisory om TanStack siger, at ethvert udvikler- eller CI-miljø, der installerede en berørt version, "bør betragtes som kompromitteret." Tjek fra den første udgivelse af den inficerede version, ikke fra den dag, bølgen blev annonceret.

Rotér legitimationsoplysninger. Rotér alt, hvad installationen kunne nå. I TanStack-bølgen betød det legitimationsoplysninger til AWS, GCP, Kubernetes, Vault, GitHub, npm og SSH ifølge postmortem-rapporten. Gennemgå cloud-audit-logs for aktivitet fra de berørte hosts under og efter installationsvinduet.

Ryd caches og artefakter. Søg i artefakt-repositories og afhængighedsproxyer efter cachede kopier af de berørte versioner, og slet dem, som CISA anbefaler. Slet node_modules, npm-cachen og CI-caches; i TanStack-bølgen kom angriberen ind via en forgiftet cache af pnpm-storen, så tjek Actions-caches såvel som pakke-caches. Genbyg og genudgiv alt, hvad et kompromitteret job har produceret.

Hvad du skal sige til dine kunder

Hvis du leverer software bygget med npm, vil kunderne spørge, om dit produkt bærer ormen, eller om din release-pipeline er blevet kompromitteret. Svar med, hvad du tjekkede, og hvad du gjorde:

Vi har tjekket hvert build af [produkt] mellem [dato] og [dato] mod de offentliggjorte lister over npm-pakkeversioner inficeret med Shai-Hulud. [Intet build installerede en berørt version.] / [Builds af [komponent] den [dato] installerede [pakke@version]; vi har roteret de legitimationsoplysninger, disse builds havde adgang til, genbygget [produkt] fra rene afhængigheder og udgivet [version]. Release [x til y] er berørt; opgradér venligst til [version].] Vi opdaterer denne meddelelse, hvis nye bølger ændrer billedet.

Hvis du stadig er ved at tjekke, så sig det, og sig, hvornår du svarer. En første udtalelse og skabeloner til hvert udfald finder du i sådan svarer du kunder efter et forsyningskædeangreb.

Sådan hjælper CRACI

CRACI kører dine GitHub Actions-jobs på sine egne runnere (runs-on: craci) og registrerer hver pakke, hvert job faktisk henter fra npm og andre registre, inklusive pakker gendannet fra caches. Den registrering dækker de blinde vinkler ovenfor: npx-værktøjer, globale installationer og installationer i build-scripts dukker alle op, fordi registreringen kommer fra det, jobbet downloadede, ikke fra det, en lockfile deklarerede. Når en ny bølge bliver annonceret, slår du de berørte versioner op i de builds, CRACI har registreret, i stedet for at rekonstruere dem ud fra lockfiles og logs, og hvert jobs netværkstrace viser, hvad det oprettede forbindelse til. Build-historikken gemmer hvert tidligere build med dets SBOM, så du ser, hvilke builds der indeholdt en inficeret version, og i hvilket tidsvindue du var eksponeret, inden for din opbevaringsperiode.

CRACI's egress-politik begrænser, hvad et job kan oprette forbindelse til, valideres, før jobbet starter, blokerer ved fejl og giver dig en alarm, når et job forsøger at nå en destination, politikken ikke tillader. Inficerede releases med en offentliggjort CVE, som TanStack-bølgen, bliver markeret som enhver anden sårbarhed, og policy gates kan blokere builds, der indeholder dem. CRACI vurderer ikke pakker for ondsindet adfærd og markerer ikke en ny inficeret version, før der findes en advisory; kombinér det med et værktøj til pakkeomdømme, hvis du vil have inficerede versioner blokeret før installation.

Se pakkeregistreringen af et af dine egne builds

Andre hændelser: alle sårbarheder.

Kilder og opdateringer

Primære kilder og original research:

Opdateringer:

  • 2026-11-11: Tilføjet EPSS, CWE-klassen og krediteringen i boksen, tjek, du kan kopiere og indsætte, samt afsnit om, hvordan ormen virker, hvordan du verificerer oprydningen, og hvordan du tjekker for kompromittering, med indikatorer pr. bølge.
  • 2026-11-11: Flyttet fra bloggen til sårbarhedssektionen. Tilføjet tensorlake-bølgen den 8. oktober 2026, datoen for genkomsten i september 2026, CVE-2026-45321 og dens KEV-optagelse for TanStack-bølgen samt GitHubs package firewall.
  • 2026-09-25: Først udgivet på CRACI-bloggen.

Ofte stillede spørgsmål

Er Shai-Hulud stadig aktiv?

Ja. Den er vendt tilbage i bølger siden september 2025, og ophavsmændene offentliggjorde kildekoden i maj 2026, så senere bølger kan komme fra andre grupper. Den seneste bølge, der er dækket her, ramte pakken tensorlake den 8. oktober 2026.

Har Shai-Hulud en CVE?

Ormen som helhed har ingen. GitHub udgiver malware-advisories for enkelte inficerede pakker, og TanStack-bølgen i maj 2026 har CVE-2026-45321, som CISA føjede til sit katalog over Known Exploited Vulnerabilities den 27. maj 2026.

Betyder en ren lockfile, at vi ikke var berørt?

Nej. Pakker installeret med npx, globale installationer, installationer i build-scripts og Dockerfiles, ældre lockfiles på andre branches og gendannede caches kan alle trække en inficeret version ind, som den aktuelle lockfile ikke viser. Du har brug for en registrering af, hvad hvert build faktisk hentede.

Hvordan tjekker jeg, om jeg er berørt?

Hent de berørte pakkeversioner for bølgen, søg i hver lockfile på hver branch, der kørte CI i perioden, søg i CI-logs og SBOM'er pr. build efter versionerne og payloadens filnavne, og tjek din GitHub-organisation for repositories, workflows og selvhostede runnere, du ikke har oprettet.

Hvad gør jeg, hvis et build installerede en inficeret version?

Behandl jobbet som kompromitteret. Rotér hver secret, det kunne læse, inklusive npm-tokens, GitHub-tokens, cloud-legitimationsoplysninger og deploy-nøgler, slet GitHub Actions-caches og npm-caches, og tjek for workflows, repositories og runnere, som ormen har tilføjet.

Hvordan ved jeg, om ormen kørte i vores builds?

Søg i build-logs, lockfiles og installerede pakker efter bølgens indikatorer: payload-filer som bundle.js, setup_bun.js, bun_environment.js eller router_init.js, repositories og workflows, du ikke har oprettet, en selvhostet runner ved navn SHA1HULUD og forbindelser til webhook.site eller getsession.org. Ethvert job, der installerede en berørt version, kørte payloaden, så behandl det som kompromitteret, selv hvis du ikke finder andet.