131% fler rapporterade CVE:er per dag 2026 än förra året.
← Alla sårbarheter

Shai-Hulud: npm-masken och så kontrollerar du om dina byggen berörs

Shai-Hulud är en självspridande mask som infekterar npm-paket, en attack mot mjukvarans leveranskedja som värvar varje offer för att sprida den vidare. Den körs när ett paket installeras, stjäl npm-tokens, GitHub-tokens och molnnycklar från maskinen och använder npm-tokenen för att publicera infekterade versioner av offrets andra paket. Sedan den första vågen i september 2025 har den kommit tillbaka gång på gång, och i maj 2026 publicerade upphovsmännen källkoden, så varje våg sedan dess kan vara en annan grupp. Om något av dina byggen installerade en infekterad version ska du behandla det jobbet som komprometterat och rotera varje hemlighet det kunde läsa.

Den största skadan sker i CI. I vågen i november 2025 fann Wiz att bara 23 % av infektionerna fanns på utvecklares datorer, resten mestadels i CI/CD-pipelines, där GitHub Actions var vanligast. Ett bygge installerar hundratals paket, har hemligheterna som krävs för att publicera och driftsätta, och kör varje installationsskript utan att någon tittar på. Så när en ny våg tillkännages ställer varje team samma fråga: installerade något av våra byggen en infekterad version?

Vad som hände

NärVågVad som skilde sig
September 2025Shai-HuludEtt postinstall-skript körde en bundle.js på 3,6 MB, använde TruffleHog för att hitta hemligheter och publicerade om upp till 20 av underhållarens paket. Fler än 500 paket drabbades, däribland @ctrl/tinycolor och 18 @crowdstrike-paket. CISA publicerade en varning den 23 september.
November 2025Sha1-Hulud: The Second ComingFlyttade till preinstall, installerade körmiljön Bun för att köra en nyttolast på 10 MB, försåg upp till 100 paket per underhållare med bakdörrar och registrerade infekterade maskiner som egenhostade GitHub-runners. Paket från Zapier, ENS, PostHog och Postman drabbades; Wiz räknade fler än 25 000 repon med läckta hemligheter.
December 2025”3.0”-testEn modifierad version i ett enda paket, utan nämnvärd spridning.
Maj 2026TanStack84 skadliga versioner av 42 @tanstack-paket. Angriparen stal aldrig någon npm-token: ett pull_request_target-arbetsflöde, en förgiftad Actions-cache och en OIDC-token som lästes ur runnerns minne gjorde att angriparen kunde publicera via TanStacks egen trusted publishing. Spåras som CVE-2026-45321, tillagd i CISA:s KEV-katalog den 27 maj.
Maj 2026AntV639 versioner av 323 paket. På Linux-runners i GitHub Actions läste nyttolasten hemligheter ur runnerprocessens minne och kringgick loggmaskeringen. GitHub ogiltigförklarade 61 274 npm-skrivtokens.
Juni 2026Miasma32 @redhat-cloud-services-paket, publicerade via injicerade arbetsflöden med giltig proveniens. Varje infektion bar en unikt krypterad nyttolast, så hashbaserad upptäckt misslyckades.
Augusti 2026CHAINDROPPopulära cachepaket som keyv, cacheable och cache-manager, med totalt hundratals drabbade paket. Masken bet sig fast via konfigurationsfiler för editorer och AI-assistenter i repot.
September 2026ÅterkomstDen 7 september kom nyttolasten tillbaka i fyra paket som tog sig förbi npms nya skanning efter skadlig kod vid publicering.
Oktober 2026tensorlakeDen 8 oktober publicerades [email protected] med masken. OX Security såg nya angriparnycklar och tror att det rör sig om ”en oberoende hotaktör eller en ny grupp som använder namnet Shai-Hulud”.

Vad masken gör i ett CI-jobb

Detaljerna ändras mellan vågorna, men mönstret består:

  1. Den körs vid installation. Ett preinstall- eller postinstall-skript körs så snart npm install eller npm ci når den infekterade versionen. Inget i din kod behöver importera den.
  2. Den samlar in hemligheter. Miljövariabler, .npmrc-tokens, molnuppgifter från metadatatjänsten och hemlighetshanterare, och allt som TruffleHog hittar på disk. Senare vågor läste GitHub Actions-runnerns minne direkt, vilket ger hemligheter som aldrig exponerades för steget och som maskeringen skulle ha dolt i loggar.
  3. Den för ut dem. Tidiga vågor skapade publika GitHub-repon uppkallade efter masken och pushade ett arbetsflöde som skickade varje repohemlighet till webhook.site. Senare vågor använde angriparservrar och meddelandenätverk.
  4. Den sprider sig och stannar kvar. Med en stulen npm-token publicerar den infekterade versioner av andra paket. Med en GitHub-token lägger den till arbetsflöden, registrerar egenhostade runners eller planterar konfigurationsfiler som körs nästa gång någon öppnar repot.
  5. Den beter sig annorlunda i CI. Nyttolasten från november 2025 letade efter GITHUB_ACTIONS, BUILDKITE, CIRCLE_SHA1 och liknande variabler, och i CI kördes den synkront och blev klar innan bygget gick vidare.

Vem som berörs

Alla vars maskiner eller byggen installerade en infekterad version medan den fanns i registret: utvecklares datorer, CI-jobb, Docker-byggen som kör npm install och egenhostade runners. Du behöver inte vara direkt beroende av paketet; ett transitivt beroende eller ett verktyg som körs med npx räcker. Varje våg har sin egen lista över paket och versioner, och infekterade versioner tas oftast bort inom timmar eller dagar, så det som spelar roll är om något installerade dem under det fönstret.

Så fungerar det

Shai-Hulud är ingen bugg i npm. Det är skadlig kod som publiceras inuti annars legitima paket, och som GitHubs säkerhetsmeddelanden om skadlig kod för de infekterade paketen klassar som inbäddad skadlig kod (CWE-506). Varje våg kombinerar några tekniker:

  • Körning vid installation. npm kör ett pakets livscykelskript, som preinstall, postinstall och prepare, under installationen, med användarens eller CI-jobbets behörigheter. En infekterad version behöver bara finnas i beroendeträdet; inget behöver importera den.
  • Spridning med stulna publiceringsrättigheter. Med en npm- eller GitHub-token som tagits från ett offer publicerar masken nya versioner av offrets andra paket med nyttolasten tillagd, så varje infekterad underhållare infekterar fler paket.
  • Publicering via offrets egen release-pipeline (TanStack, maj 2026). Enligt GitHubs säkerhetsmeddelande kedjade angriparen ihop ett pull_request_target-arbetsflöde, en GitHub Actions-cache som förgiftats över förtroendegränsen mellan fork och basrepo, och en OIDC-token som lästes ur runnerns minne, och publicerade via TanStacks trusted publishing utan att ändra publiceringsarbetsflödet. De infekterade versionerna deklarerade ett valfritt beroende på en commit i en fork, vars prepare-skript körde nyttolasten och sedan avsiktligt misslyckades, så att npm kastade beroendet och inga spår lämnades i node_modules.
  • Giltig proveniens (Miasma, juni 2026). Injicerade arbetsflöden publicerade från offrens egna repon, så de infekterade versionerna hade giltig proveniens, och en unikt krypterad nyttolast per infektion gick förbi hashbaserad upptäckt.

Så kontrollerar du om du berörs

  1. Hämta listan över berörda versioner för vågen. Säkerhetsleverantörer publicerar dem inom några timmar; Datadog har till exempel ett repo med indikatorer på kompromettering med paket- och versionslistor.
  2. Sök i varje lockfil, på varje gren som körde CI under perioden, inte bara standardgrenen.
  3. Sök i själva byggena. Leta i CI-loggar efter setup_bun.js, bun_environment.js eller andra filnamn som nämns i säkerhetsmeddelandet, och efter installationsutdata som nämner de berörda versionerna. Om du sparar SBOM:er per bygge, sök efter versionerna i dem.
  4. Kontrollera din GitHub-organisation efter nya repon uppkallade efter masken, arbetsflödesfiler du inte har skrivit (till exempel shai-hulud-workflow.yml, discussion.yaml eller formatter_*.yml) och egenhostade runners du inte har registrerat.

Kommandon du kan anpassa. För att söka i lockfilerna på varje fjärrgren lägger du vågens berörda versioner i affected.txt, en name@version per rad:

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

För att lista organisationens egenhostade runners, nya repon som nämner masken och de arbetsflödesfiler som masken är känd för att skapa (ersätt 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

Repon kan också ha egna runners (gh api repos/ORG/REPO/actions/runners), och tidiga vågor skapade repon på de personliga kontona för de utvecklare vars tokens de stal, så be dina utvecklare att kontrollera sina.

Därför avgör inte lockfilen saken

Standardrådet är att söka efter de berörda versionerna i package-lock.json, yarn.lock eller pnpm-lock.yaml. Gör det först, men en ren lockfil bevisar inte ett rent bygge:

  • Paket som installeras utanför lockfilen. npx some-tool installerar ett paket som inte är ett projektberoende i npm-cachen. npm install -g i ett installationssteg, ett verktyg som installeras i ett byggskript och en npm install i en Dockerfile går alla förbi projektets lockfil.
  • Lockfilen på grenen i dag är inte lockfilen som kördes. Ett bygge för tre veckor sedan använde det som lockfilen sa då, på den gren som utlöste det. Pull requests med beroendeuppdateringar kör CI mot nya versioner innan någon granskar dem.
  • Cachar återställer det som installerades tidigare. En återställd node_modules eller npm-cache kan innehålla en version som den aktuella lockfilen inte längre nämner.
  • npm ci installerar det lockfilen säger. Om den infekterade versionen redan var låst installerar en strikt installation den troget.
  • En korrekt publicerad version kan ändå vara skadlig. TanStack och Miasma komprometterades via sina egna release-arbetsflöden, och Miasmas infekterade versioner hade giltig proveniens. Att en version publicerades korrekt säger inte att den är ren.

Det du faktiskt behöver är en registrering av vilka paketversioner varje bygge hämtade, över varje repo och varje gren, under hela perioden en infekterad version fanns i registret. Se är vi drabbade? så bedömer du en ny CVE över alla dina byggen.

Så åtgärdar du det, och vad du gör om du inte kan patcha än

  1. Om något bygge installerade en berörd version, behandla jobbet som komprometterat. Rotera varje hemlighet det kunde läsa: npm-tokens, GitHub-tokens, molnuppgifter och driftsättningsnycklar. Eftersom senare vågor publicerade stulna hemligheter på andra offers konton ska du inte utgå från att du är säker bara för att inget dök upp på dina egna.
  2. Rensa cacharna. Ta bort GitHub Actions-cachar och npm-cachen på utvecklardatorer och egenhostade runners, så att nästa bygge installerar rena 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. Lås till en ren version och ta bort de arbetsflöden, repon, runners och konfigurationsfiler som masken lade till.

Verifiera saneringen

  1. Varje lockfil löser upp till rena versioner. På varje gren visar npm ls <package> (eller pnpm why eller yarn why) vilken version som är installerad; ingen får finnas på vågens lista.
  2. En ren installation innehåller inga nyttolastfiler. Efter cacherensningen ovan installerar du igen och letar efter de nyttolastfiler som nämns i säkerhetsmeddelandena:
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 gamla inloggningsuppgifterna fungerar inte längre. Återkallade npm-tokens finns inte längre i npm token list, gamla GitHub-tokens och driftsättningsnycklar är borttagna, och molnnycklar är inaktiverade, inte bara ersatta.
  2. Cacharna är borta. gh cache list visar ingen cache som skapats före saneringen i något berört repo.
  3. Inget som masken lade till finns kvar: inga repon, arbetsflödesfiler, egenhostade runners eller konfigurationsfiler för editorer och AI-assistenter som du inte har skapat.
  4. Nästa bygge är rent. Kontrollera vilka versioner det faktiskt installerade, i dess logg eller SBOM per bygge, inte bara i lockfilen.

Så minskar du skadan nästa gång

  • Stäng av installationsskript där du kan. npm v12, som släpptes i juli 2026, inaktiverar installationsskript som standard och låter dig tillåta dem per paket. pnpm och Yarn har liknande kontroller.
  • Vänta innan du tar in nya versioner. De flesta infekterade versioner hittas och tas bort inom timmar eller dagar. En karenstid för beroendeuppdateringar, som både Dependabot och Renovate stöder, håller dem borta från dina byggen. GitHubs paketbrandvägg, i offentlig förhandsversion för GitHub Enterprise Cloud sedan den 20 oktober 2026, kontrollerar npm-installationer efter skadliga paket och erbjuder också karenstider.
  • Publicera med trusted publishing och kortlivade tokens. npm återkallade alla klassiska tokens i december 2025. Använd OIDC-baserad trusted publishing från CI i stället för långlivade tokens, och håll publiceringsarbetsflöden åtskilda från bygg- och testarbetsflöden.
  • Härda arbetsflödena som masken missbrukar. Undvik pull_request_target, lås actions till commit-SHA:er och ge GITHUB_TOKEN bara de behörigheter varje jobb behöver. Se hemligheter i GitHub Actions: så läcker de och så låser du dem.
  • Begränsa vart byggen kan ansluta. Ett bygge behöver sina paketregister och några API:er. Om det bara kan nå dem misslyckas maskens anrop till webhook.site eller till dess egna servrar. Se hur en egress-policy stoppar exfiltrering från GitHub Actions. Det stoppar inte exfiltrering via en värd som bygget legitimt använder, som GitHubs eget API, vilket vissa vågor förlitade sig på.

Kontrollera om du har komprometterats

Shai-Hulud är skadlig per design: varje maskin eller jobb som installerade en infekterad version körde den. Indikatorerna skiljer sig mellan vågorna, så använd säkerhetsmeddelandet för den våg du kontrollerar. Från primärkällor:

VågIndikatorerKälla
September 2025Nyttolasten bundle.js, SHA-256 46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09; arbetsflödet .github/workflows/shai-hulud-workflow.yml på en gren shai-hulud; ett publikt repo med namnet Shai-Hulud; anslutningar till webhook.siteStepSecurity, CISA
November 2025setup_bun.js, SHA-1 d1829b4708126dcc7bea7437c04d1f10eacd4a16; bun_environment.js, SHA-1 d60ec97eea19fffb4809bc35b91033b52490ca11 eller 3d7570d14d34b0ba137d502f042b27b0f37a59fa; repon med cloud.json, contents.json, environment.json, truffleSecrets.json och actionsSecrets.json; arbetsflödena .github/workflows/discussion.yaml och formatter_*.yml; en egenhostad runner med namnet SHA1HULUDWiz
Maj 2026, TanStack"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" i optionalDependencies; nyttolasten router_init.js (cirka 2,3 MB) i paketets rot; hjälpskriptet tanstack_runner.js; anslutningar till filev2.getsession.org och seed1.getsession.org till seed3.getsession.org, och nedladdningar i andra steget från litter.catbox.moeGHSA-g7cv-rxg3-hmpx

Senare vågor ändrade nyttolasten för varje infektion, så en filhash som inte matchar bevisar ingenting; matcha först på paketversioner.

När du ska utgå från att du har komprometterats. Behandla varje värd och CI-jobb som installerade en berörd version som komprometterad, även om installationen misslyckades eller inte lämnade något i node_modules. GitHubs säkerhetsmeddelande om TanStack säger att varje utvecklar- eller CI-miljö som installerade en berörd version ”bör betraktas som komprometterad.” Kontrollera från den första publiceringen av den infekterade versionen, inte från dagen då vågen tillkännagavs.

Rotera inloggningsuppgifter. Rotera allt som installationen kunde nå. I TanStack-vågen innebar det inloggningsuppgifter för AWS, GCP, Kubernetes, Vault, GitHub, npm och SSH, enligt postmortem-rapporten. Granska molnets granskningsloggar efter aktivitet från de berörda värdarna under och efter installationsfönstret.

Rensa cachar och artefakter. Sök i artefaktrepon och beroendeproxyer efter cachade kopior av de berörda versionerna och ta bort dem, som CISA rekommenderar. Ta bort node_modules, npm-cachen och CI-cachar; i TanStack-vågen tog sig angriparen in via en förgiftad cache för pnpm-lagret, så kontrollera Actions-cachar såväl som paketcachar. Bygg om och släpp på nytt allt som ett komprometterat jobb producerade.

Vad du ska säga till dina kunder

Om du levererar programvara som byggs med npm kommer kunderna att fråga om din produkt bär på masken eller om din release-pipeline har komprometterats. Svara med vad du kontrollerade och vad du gjorde:

Vi har kontrollerat varje bygge av [produkt] mellan [datum] och [datum] mot de publicerade listorna över npm-paketversioner infekterade med Shai-Hulud. [Inget bygge installerade en berörd version.] / [Byggen av [komponent] den [datum] installerade [paket@version]; vi har roterat de inloggningsuppgifter som de byggena hade åtkomst till, byggt om [produkt] från rena beroenden och släppt [version]. Release [x till y] berörs; uppgradera till [version].] Vi uppdaterar det här meddelandet om nya vågor förändrar läget.

Om du fortfarande kontrollerar, säg det, och säg när du kommer att svara. Ett första uttalande och mallar för varje utfall finns i hur du svarar kunder efter en attack mot leveranskedjan.

Så hjälper CRACI

CRACI kör dina GitHub Actions-jobb på sina egna runners (runs-on: craci) och registrerar varje paket som varje jobb faktiskt hämtar från npm och andra register, inklusive paket som återställs från cachar. Den registreringen täcker de blinda fläckarna ovan: npx-verktyg, globala installationer och installationer i byggskript syns alla, eftersom registreringen kommer från vad jobbet laddade ner, inte från vad en lockfil deklarerade. När en ny våg tillkännages slår du upp de berörda versionerna i de byggen CRACI har registrerat i stället för att rekonstruera dem från lockfiler och loggar, och varje jobbs nätverksspårning visar vart det anslöt. Bygghistoriken sparar varje tidigare bygge med dess SBOM, så att du ser vilka byggen som innehöll en infekterad version och under vilket fönster du var exponerad, inom din lagringsperiod.

CRACI:s egress-policy begränsar vart ett jobb kan ansluta, valideras innan jobbet startar, blockerar vid fel och larmar dig när ett jobb försöker nå en destination som policyn inte tillåter. Infekterade releaser med en publicerad CVE, som TanStack-vågen, flaggas som vilken annan sårbarhet som helst, och policyspärrar kan blockera byggen som innehåller dem. CRACI poängsätter inte paket utifrån skadligt beteende och flaggar inte en ny infekterad version innan ett säkerhetsmeddelande finns; kombinera det med ett verktyg för paketrykte om du vill att infekterade versioner ska blockeras före installation.

Se paketregistreringen för ett av dina egna byggen

Andra incidenter: alla sårbarheter.

Källor och uppdateringar

Primärkällor och originalforskning:

Uppdateringar:

  • 2026-11-11: Lade till EPSS, CWE-klassen och krediteringen i rutan, kontroller att kopiera och klistra in, samt avsnitt om hur masken fungerar, hur du verifierar saneringen och hur du kontrollerar om du har komprometterats, med indikatorer per våg.
  • 2026-11-11: Flyttad från bloggen till avsnittet om sårbarheter. Lade till vågen med tensorlake den 8 oktober 2026, datumet för återkomsten i september 2026, CVE-2026-45321 och dess KEV-listning för TanStack-vågen samt GitHubs paketbrandvägg.
  • 2026-09-25: Först publicerad på CRACI-bloggen.

Vanliga frågor

Är Shai-Hulud fortfarande aktiv?

Ja. Den har kommit tillbaka i vågor sedan september 2025, och upphovsmännen publicerade källkoden i maj 2026, så senare vågor kan komma från andra grupper. Den senaste vågen som tas upp här drabbade paketet tensorlake den 8 oktober 2026.

Har Shai-Hulud en CVE?

Masken som helhet har ingen. GitHub publicerar säkerhetsmeddelanden om skadlig kod för enskilda infekterade paket, och TanStack-vågen i maj 2026 har CVE-2026-45321, som CISA lade till i sin katalog Known Exploited Vulnerabilities den 27 maj 2026.

Betyder en ren lockfil att vi inte drabbades?

Nej. Paket som installeras med npx, globala installationer, installationer i byggskript och Dockerfiles, äldre lockfiler på andra grenar och återställda cachar kan alla dra in en infekterad version som den aktuella lockfilen inte visar. Du behöver en registrering av vad varje bygge faktiskt hämtade.

Hur kontrollerar jag om jag är drabbad?

Hämta de berörda paketversionerna för vågen, sök i varje lockfil på varje gren som körde CI under perioden, sök i CI-loggar och SBOM:er per bygge efter versionerna och nyttolastens filnamn, och kontrollera din GitHub-organisation efter repon, arbetsflöden och egenhostade runners som du inte har skapat.

Vad gör jag om ett bygge installerade en infekterad version?

Behandla jobbet som komprometterat. Rotera varje hemlighet det kunde läsa, inklusive npm-tokens, GitHub-tokens, molnuppgifter och driftsättningsnycklar, ta bort GitHub Actions-cachar och npm-cachar, och leta efter arbetsflöden, repon och runners som masken har lagt till.

Hur vet jag om masken kördes i våra byggen?

Sök i byggloggar, lockfiler och installerade paket efter vågens indikatorer: nyttolastfiler som bundle.js, setup_bun.js, bun_environment.js eller router_init.js, repon och arbetsflöden som du inte har skapat, en egenhostad runner med namnet SHA1HULUD och anslutningar till webhook.site eller getsession.org. Varje jobb som installerade en berörd version körde nyttolasten, så behandla det som komprometterat även om du inte hittar något annat.