Shai-Hulud is een zichzelf verspreidende worm die npm-pakketten infecteert, een aanval op de softwareketen die elk slachtoffer inzet om hem verder te verspreiden. Hij draait wanneer een pakket wordt geïnstalleerd, steelt npm-tokens, GitHub-tokens en cloudsleutels van de machine, en gebruikt het npm-token om geïnfecteerde versies van de andere pakketten van het slachtoffer te publiceren. Sinds de eerste golf in september 2025 is hij herhaaldelijk teruggekomen, en in mei 2026 publiceerden de makers de broncode, dus elke golf sindsdien kan van een andere groep zijn.

De meeste schade ontstaat in CI. Bij de golf van november 2025 ontdekte Wiz dat slechts 23% van de infecties op machines van ontwikkelaars zat: de overige 77% zat in CI/CD-pipelines, met GitHub Actions als meest voorkomende. Een build installeert honderden pakketten, bevat de secrets die nodig zijn om te publiceren en te deployen, en voert elk installatiescript uit zonder dat iemand meekijkt.

Wanneer een nieuwe golf wordt aangekondigd, stelt elk team dezelfde vraag: heeft een van onze builds een geïnfecteerde versie geïnstalleerd? Dit bericht behandelt wat de worm doet, waarom het gebruikelijke antwoord ("controleer de lockfile") onvolledig is, en hoe u de vraag goed beantwoordt.

De golven tot nu toe

WanneerNaamWat er anders was
September 2025Shai-HuludEen postinstall-script voerde een bundle.js van 3,6 MB uit, gebruikte TruffleHog om secrets te vinden en publiceerde tot 20 pakketten van de maintainer opnieuw. Meer dan 500 pakketten werden getroffen, waaronder @ctrl/tinycolor en 18 @crowdstrike-pakketten.
November 2025Sha1-Hulud: The Second ComingVerhuisde naar preinstall, installeerde de Bun-runtime om een payload van 10 MB uit te voeren, voorzag tot 100 pakketten per maintainer van een backdoor en registreerde geïnfecteerde machines als self-hosted GitHub-runners. Pakketten van Zapier, ENS, PostHog en Postman werden getroffen; Wiz telde meer dan 25.000 repositories met gelekte secrets.
December 2025"3.0"-testEen aangepaste versie in één pakket, zonder noemenswaardige verspreiding.
Mei 2026TanStack84 kwaadaardige versies van 42 @tanstack-pakketten. De aanvaller stal nooit een npm-token: een pull_request_target-workflow, een vergiftigde Actions-cache en een OIDC-token dat uit het runnergeheugen werd gelezen, stelden hem in staat te publiceren via TanStacks eigen trusted publishing.
Mei 2026AntV639 versies van 323 pakketten. Op Linux-runners van GitHub Actions las de payload secrets uit het geheugen van het runnerproces, waardoor log-masking werd omzeild. GitHub maakte 61.274 npm-schrijftokens ongeldig.
Juni 2026Miasma32 @redhat-cloud-services-pakketten, gepubliceerd via geïnjecteerde workflows met geldige herkomstgegevens. Elke infectie bevatte een uniek versleutelde payload, waardoor detectie op basis van hashes faalde.
Augustus 2026CHAINDROPPopulaire cachingpakketten zoals keyv, cacheable en cache-manager, met in totaal honderden getroffen pakketten. Hij bleef aanwezig via configuratiebestanden van editors en AI-assistenten in de repository.

De payload kwam in september 2026 opnieuw terug, in vier pakketten die langs de nieuwe malwarescan van npm bij publicatie kwamen.

Wat de worm in een CI-job doet

De details veranderen per golf, maar het patroon blijft:

  1. Hij draait bij de installatie. Een preinstall- of postinstall-script wordt uitgevoerd zodra npm install of npm ci de geïnfecteerde versie bereikt. Niets in uw code hoeft hem te importeren.
  2. Hij verzamelt secrets. Omgevingsvariabelen, .npmrc-tokens, cloudinloggegevens uit de metadataservice en secretmanagers, en alles wat TruffleHog op schijf vindt. Latere golven lazen rechtstreeks het geheugen van de GitHub Actions-runner, wat secrets oplevert die nooit aan de stap zijn blootgesteld en die masking in logs had verborgen.
  3. Hij voert ze af. Vroege golven maakten openbare GitHub-repositories aan die naar de worm waren genoemd en pushten een workflow die elk repositorysecret naar webhook.site stuurde. Latere golven gebruikten servers van aanvallers en berichtennetwerken.
  4. Hij verspreidt zich en blijft. Met een gestolen npm-token publiceert hij geïnfecteerde versies van andere pakketten. Met een GitHub-token voegt hij workflows toe, registreert hij self-hosted runners of plaatst hij configuratiebestanden die draaien zodra iemand de repository opent.
  5. Hij gedraagt zich anders in CI. De payload van november 2025 controleerde op GITHUB_ACTIONS, BUILDKITE, CIRCLE_SHA1 en vergelijkbare variabelen, en draaide in CI synchroon, zodat hij zijn werk afrondde voordat de build verderging.

Waarom de lockfile het niet beslist

Het standaardadvies is om package-lock.json, yarn.lock of pnpm-lock.yaml te doorzoeken op de getroffen versies. Doe dat eerst, maar een schone lockfile bewijst geen schone build:

  • Pakketten die buiten de lockfile om zijn geïnstalleerd. npx some-tool installeert een pakket dat geen projectafhankelijkheid is in de npm-cache. npm install -g in een setupstap, een tool die in een buildscript wordt geïnstalleerd en een npm install in een Dockerfile omzeilen allemaal de lockfile van het project.
  • De lockfile op de branch van vandaag is niet de lockfile die draaide. Een build van drie weken geleden gebruikte wat de lockfile toen zei, op de branch die de build startte. Pull requests voor afhankelijkheidsupdates draaien CI tegen nieuwe versies voordat iemand ze beoordeelt.
  • Caches zetten terug wat eerder is geïnstalleerd. Een teruggezette node_modules of npm-cache kan een versie bevatten die de huidige lockfile niet meer noemt.
  • npm ci installeert wat de lockfile zegt. Was de geïnfecteerde versie al vastgelegd, dan installeert een strikte installatie die braaf.
  • Een correct gepubliceerde versie kan toch kwaadaardig zijn. TanStack en Miasma werden gecompromitteerd via hun eigen releaseworkflows, en de geïnfecteerde versies van Miasma hadden geldige herkomstgegevens. Controleren of een versie correct is gepubliceerd, vertelt u niet dat die schoon is.

Wat u werkelijk nodig hebt, is een record van welke pakketversies elke build heeft opgehaald, over elke repository en elke branch, voor de hele periode dat een geïnfecteerde versie in het register stond.

Hoe u controleert of u getroffen bent

  1. Haal de lijst met getroffen versies op voor de golf. Beveiligingsleveranciers publiceren die binnen enkele uren; Datadog onderhoudt bijvoorbeeld een repository met indicators of compromise, met lijsten van pakketten en versies.
  2. Doorzoek elke lockfile, op elke branch die tijdens de periode CI heeft gedraaid, niet alleen op de standaardbranch.
  3. Doorzoek de builds zelf. Zoek in CI-logs naar setup_bun.js, bun_environment.js of andere bestandsnamen uit het advies, en naar installatie-uitvoer waarin de getroffen versies staan. Bewaart u SBOM's per build, doorzoek die dan op de versies.
  4. Controleer uw GitHub-organisatie op nieuwe repositories die naar de worm zijn genoemd, workflowbestanden die u niet hebt geschreven (bijvoorbeeld shai-hulud-workflow.yml, discussion.yaml of formatter_*.yml) en self-hosted runners die u niet hebt geregistreerd.
  5. Maak de caches leeg. Verwijder GitHub Actions-caches en de npm-cache op machines van ontwikkelaars en self-hosted runners, zodat de volgende build schone versies installeert.
  6. Heeft een build een getroffen versie geïnstalleerd, behandel de job dan als gecompromitteerd. Roteer elk secret dat de job kon lezen: npm-tokens, GitHub-tokens, cloudinloggegevens en deploy keys. Omdat latere golven gestolen secrets in de accounts van andere slachtoffers publiceerden, moet u er niet van uitgaan dat u veilig bent omdat er in uw eigen account niets is verschenen.

Hoe u de schade de volgende keer beperkt

  • Schakel installatiescripts uit waar dat kan. npm v12, uitgebracht in juli 2026, schakelt installatiescripts standaard uit en laat u ze per pakket toestaan. pnpm en Yarn bieden vergelijkbare opties.
  • Wacht voordat u nieuwe versies overneemt. De meeste geïnfecteerde versies worden binnen uren of dagen gevonden en verwijderd. Een cooldown op afhankelijkheidsupdates, die zowel Dependabot als Renovate ondersteunen, houdt ze uit uw builds.
  • Publiceer met trusted publishing en kortlevende tokens. npm trok in december 2025 alle classic tokens in. Gebruik op OIDC gebaseerde trusted publishing vanuit CI in plaats van langlevende tokens, en houd publicatieworkflows gescheiden van build- en testworkflows.
  • Beveilig de workflows die de worm misbruikt. Vermijd pull_request_target, pin actions vast op commit-SHA's en geef GITHUB_TOKEN alleen de rechten die elke job nodig heeft. Zie GitHub Actions-secrets: hoe ze lekken en hoe u ze afschermt.
  • Beperk waarmee builds verbinding kunnen maken. Een build heeft zijn pakketregisters en een paar API's nodig. Kan hij alleen die bereiken, dan mislukken de aanroepen van de worm naar webhook.site of zijn eigen servers. Zie hoe een egressbeleid exfiltratie uit GitHub Actions stopt. Dit stopt geen exfiltratie via een host die de build legitiem gebruikt, zoals GitHubs eigen API, waarop sommige golven steunden.

Waar CRACI in past

CRACI draait uw GitHub Actions-jobs op eigen runners (runs-on: craci), en een proxy op de runner die pakketten herkent, legt elk pakket vast dat elke job daadwerkelijk ophaalt uit npm en andere registers, inclusief pakketten die uit caches zijn teruggezet. Dat record dekt de bovenstaande blinde vlekken: npx-tools, globale installaties en installaties in buildscripts verschijnen allemaal, omdat het record uitgaat van wat de job heeft gedownload, niet van wat een lockfile declareerde.

Wanneer een nieuwe golf wordt aangekondigd, zoekt u de getroffen versies op in de builds die CRACI heeft vastgelegd, in plaats van ze te reconstrueren uit lockfiles en logs. Bevindingen worden gebundeld over builds en repositories heen, de inventaris van CRACI toont welke versies in welke producten zijn gegaan, en de netwerktrace van elke job toont waarmee die verbinding maakte, zodat u kunt zien of een job die een geïnfecteerde versie installeerde ook iets bereikte wat hij niet had mogen bereiken.

Het egressbeleid van CRACI beperkt waarmee een job verbinding kan maken, wordt gevalideerd voordat de job start en valt veilig dicht, en waarschuwt u wanneer een job een bestemming probeert te bereiken die het beleid niet toestaat. CRACI beoordeelt pakketten niet op kwaadaardig gedrag; combineer het met een tool voor pakketreputatie als u geïnfecteerde versies vóór de installatie wilt blokkeren.

Boek een demo om het pakketrecord van een van uw eigen builds te zien.