Shai-Hulud to samorozprzestrzeniający się robak infekujący pakiety npm, czyli atak na łańcuch dostaw oprogramowania, który każdą ofiarę angażuje do dalszego rozprzestrzeniania. Uruchamia się przy instalacji pakietu, kradnie z maszyny tokeny npm, tokeny GitHub i klucze chmurowe, a następnie używa tokenu npm do publikowania zainfekowanych wersji innych pakietów ofiary. Od pierwszej fali we wrześniu 2025 roku wielokrotnie powracał, a w maju 2026 roku jego autorzy opublikowali kod źródłowy, więc każda kolejna fala może być dziełem innej grupy.
Największe szkody powstają w CI. W fali z listopada 2025 roku firma Wiz ustaliła, że tylko 23% infekcji dotyczyło maszyn programistów: pozostałe 77% wystąpiło w pipeline'ach CI/CD, najczęściej w GitHub Actions. Build instaluje setki pakietów, przechowuje sekrety potrzebne do publikacji i wdrożeń oraz uruchamia każdy skrypt instalacyjny bez niczyjego nadzoru.
Gdy zostaje ogłoszona nowa fala, każdy zespół zadaje to samo pytanie: czy któryś z naszych buildów zainstalował zainfekowaną wersję? W tym wpisie omawiamy, co robi robak, dlaczego typowa odpowiedź („proszę sprawdzić lockfile”) jest niepełna i jak odpowiedzieć właściwie.
Dotychczasowe fale
| Kiedy | Nazwa | Co było inne |
|---|---|---|
| Wrzesień 2025 | Shai-Hulud | Skrypt postinstall uruchamiał bundle.js o rozmiarze 3,6 MB, używał TruffleHog do wyszukiwania sekretów i ponownie publikował do 20 pakietów opiekuna. Dotkniętych zostało ponad 500 pakietów, w tym @ctrl/tinycolor i 18 pakietów @crowdstrike. |
| Listopad 2025 | Sha1-Hulud: The Second Coming | Przejście na preinstall, instalacja środowiska Bun do uruchomienia ładunku o rozmiarze 10 MB, tylne furtki w maksymalnie 100 pakietach na opiekuna i rejestrowanie zainfekowanych maszyn jako runnerów self-hosted GitHub. Ucierpiały pakiety Zapier, ENS, PostHog i Postman; Wiz naliczył ponad 25 000 repozytoriów z ujawnionymi sekretami. |
| Grudzień 2025 | Test „3.0” | Zmodyfikowana wersja w jednym pakiecie, bez istotnego rozprzestrzeniania. |
| Maj 2026 | TanStack | 84 złośliwe wersje 42 pakietów @tanstack. Atakujący nigdy nie ukradł tokenu npm: workflow pull_request_target, zatruty cache Actions i token OIDC odczytany z pamięci runnera pozwoliły mu publikować przez własny trusted publishing TanStack. |
| Maj 2026 | AntV | 639 wersji 323 pakietów. Na linuksowych runnerach GitHub Actions ładunek odczytywał sekrety z pamięci procesu runnera, omijając maskowanie w logach. GitHub unieważnił 61 274 tokeny npm z prawem zapisu. |
| Czerwiec 2026 | Miasma | 32 pakiety @redhat-cloud-services, opublikowane przez wstrzyknięte workflowy z ważnymi danymi o pochodzeniu. Każda infekcja niosła unikatowo zaszyfrowany ładunek, więc wykrywanie oparte na hashach zawiodło. |
| Sierpień 2026 | CHAINDROP | Popularne pakiety cache, takie jak keyv, cacheable i cache-manager, a łącznie setki dotkniętych pakietów. Utrwalał się przez pliki konfiguracyjne edytorów i asystentów AI w repozytorium. |
Ładunek powrócił we wrześniu 2026 roku w czterech pakietach, które przeszły przez nowe skanowanie złośliwego oprogramowania w npm na etapie publikacji.
Co robak robi w zadaniu CI
Szczegóły zmieniają się między falami, ale wzorzec pozostaje taki sam:
- Działa przy instalacji. Skrypt
preinstalllubpostinstallwykonuje się, gdy tylkonpm installlubnpm cidotrze do zainfekowanej wersji. Nic w Państwa kodzie nie musi jej importować. - Zbiera sekrety. Zmienne środowiskowe, tokeny
.npmrc, poświadczenia chmurowe z usługi metadanych i menedżerów sekretów oraz wszystko, co TruffleHog znajdzie na dysku. Późniejsze fale odczytywały bezpośrednio pamięć runnera GitHub Actions, zdobywając sekrety, które nigdy nie zostały udostępnione danemu krokowi i które maskowanie ukryłoby w logach. - Wyprowadza je. Wczesne fale tworzyły publiczne repozytoria GitHub nazwane od robaka i wypychały workflow wysyłający wszystkie sekrety repozytorium do
webhook.site. Późniejsze fale korzystały z serwerów atakujących i sieci komunikacyjnych. - Rozprzestrzenia się i zostaje. Za pomocą skradzionego tokenu npm publikuje zainfekowane wersje innych pakietów. Za pomocą tokenu GitHub dodaje workflowy, rejestruje runnery self-hosted lub podkłada pliki konfiguracyjne, które uruchamiają się przy następnym otwarciu repozytorium.
- W CI zachowuje się inaczej. Ładunek z listopada 2025 roku sprawdzał zmienne
GITHUB_ACTIONS,BUILDKITE,CIRCLE_SHA1i podobne, a w CI działał synchronicznie, kończąc pracę, zanim build przeszedł dalej.
Dlaczego lockfile tego nie rozstrzyga
Standardowa rada to przeszukać package-lock.json, yarn.lock lub pnpm-lock.yaml pod kątem dotkniętych wersji. Warto zrobić to najpierw, ale czysty lockfile nie dowodzi czystego buildu:
- Pakiety instalowane poza lockfile.
npx some-toolinstaluje pakiet niebędący zależnością projektu do cache npm.npm install -gw kroku konfiguracji, narzędzie instalowane w skrypcie buildu inpm installw Dockerfile omijają lockfile projektu. - Dzisiejszy lockfile na gałęzi to nie ten, który został użyty. Build sprzed trzech tygodni użył tego, co lockfile mówił wtedy, na gałęzi, która go uruchomiła. Pull requesty z aktualizacjami zależności uruchamiają CI na nowych wersjach, zanim ktokolwiek je przejrzy.
- Cache przywraca to, co zainstalowano wcześniej. Przywrócone
node_moduleslub cache npm mogą zawierać wersję, o której bieżący lockfile już nie wspomina. npm ciinstaluje to, co mówi lockfile. Jeśli zainfekowana wersja była już przypięta w lockfile, ścisła instalacja wiernie ją zainstaluje.- Poprawnie opublikowana wersja nadal może być złośliwa. TanStack i Miasma zostały przejęte przez własne workflowy wydań, a zainfekowane wersje Miasma miały ważne dane o pochodzeniu. Sprawdzenie, że wersja została poprawnie opublikowana, nie mówi, że jest czysta.
Tak naprawdę potrzebny jest zapis tego, jakie wersje pakietów pobrał każdy build, we wszystkich repozytoriach i na wszystkich gałęziach, przez cały okres, w którym zainfekowana wersja była w rejestrze.
Jak sprawdzić, czy to Państwa dotyczyło
- Zdobyć listę dotkniętych wersji dla danej fali. Dostawcy bezpieczeństwa publikują ją w ciągu kilku godzin; Datadog prowadzi na przykład repozytorium wskaźników kompromitacji z listami pakietów i wersji.
- Przeszukać każdy lockfile na każdej gałęzi, na której w tym okresie działało CI, a nie tylko na domyślnej.
- Przeszukać same buildy. Szukać w logach CI plików
setup_bun.js,bun_environment.jslub innych nazw wymienionych w biuletynie oraz wyników instalacji wspominających dotknięte wersje. Jeśli przechowują Państwo SBOM-y dla każdego buildu, można przeszukać je pod kątem tych wersji. - Sprawdzić organizację GitHub pod kątem nowych repozytoriów nazwanych od robaka, plików workflow, których Państwo nie napisali (na przykład
shai-hulud-workflow.yml,discussion.yamllubformatter_*.yml), oraz runnerów self-hosted, których Państwo nie zarejestrowali. - Wyczyścić cache. Usunąć cache GitHub Actions i cache npm na maszynach programistów i runnerach self-hosted, aby następny build zainstalował czyste wersje.
- Jeśli jakikolwiek build zainstalował dotkniętą wersję, traktować zadanie jako przejęte. Zrotować każdy sekret, który mogło odczytać: tokeny npm, tokeny GitHub, poświadczenia chmurowe i klucze wdrożeniowe. Ponieważ późniejsze fale publikowały skradzione sekrety na kontach innych ofiar, nie należy zakładać bezpieczeństwa tylko dlatego, że nic nie pojawiło się na Państwa własnych kontach.
Jak ograniczyć szkody następnym razem
- Wyłączyć skrypty instalacyjne, gdzie to możliwe. npm v12, wydany w lipcu 2026 roku, domyślnie wyłącza skrypty instalacyjne i pozwala dopuszczać je dla poszczególnych pakietów. pnpm i Yarn oferują podobne mechanizmy.
- Odczekać przed przyjęciem nowych wersji. Większość zainfekowanych wersji jest wykrywana i usuwana w ciągu kilku godzin lub dni. Okres karencji dla aktualizacji zależności, obsługiwany zarówno przez Dependabota, jak i Renovate, trzyma je z dala od Państwa buildów.
- Publikować przez trusted publishing i krótkotrwałe tokeny. npm unieważnił wszystkie klasyczne tokeny w grudniu 2025 roku. Zamiast długotrwałych tokenów należy używać opartego na OIDC trusted publishing z CI, a workflowy publikujące trzymać oddzielnie od workflowów buildu i testów.
- Wzmocnić workflowy nadużywane przez robaka. Unikać
pull_request_target, przypinać akcje do SHA commitów i dawaćGITHUB_TOKENtylko te uprawnienia, których potrzebuje każde zadanie. Więcej w artykule Sekrety GitHub Actions: jak wyciekają i jak je zabezpieczyć. - Ograniczyć, dokąd buildy mogą się łączyć. Build potrzebuje swoich rejestrów pakietów i kilku API. Jeśli może sięgać tylko tam, wywołania robaka do
webhook.sitelub jego własnych serwerów się nie powiodą. Więcej w artykule jak polityka egress zatrzymuje eksfiltrację z GitHub Actions. Nie zatrzymuje to eksfiltracji przez host, z którego build legalnie korzysta, takiego jak własne API GitHub, na którym polegały niektóre fale.
Gdzie pasuje CRACI
CRACI uruchamia Państwa zadania GitHub Actions na własnych runnerach (runs-on: craci), a proxy rozpoznające pakiety na runnerze rejestruje każdy pakiet, który każde zadanie faktycznie pobiera z npm i innych rejestrów, łącznie z pakietami przywróconymi z cache. Ten zapis obejmuje opisane wyżej martwe pola: narzędzia npx, instalacje globalne i instalacje w skryptach buildu pojawiają się w nim, ponieważ pochodzi on z tego, co zadanie pobrało, a nie z tego, co zadeklarował lockfile.
Gdy zostaje ogłoszona nowa fala, wyszukują Państwo dotknięte wersje w buildach zarejestrowanych przez CRACI, zamiast odtwarzać je z lockfile i logów. Znaleziska są agregowane z wielu buildów i repozytoriów, inwentarz CRACI pokazuje, które wersje trafiły do których produktów, a ślad sieciowy każdego zadania pokazuje, z czym się łączyło, dzięki czemu widać, czy zadanie, które zainstalowało zainfekowaną wersję, sięgnęło też tam, gdzie nie powinno.
Polityka egress CRACI ogranicza, dokąd zadanie może się łączyć, jest walidowana przed startem zadania, w razie awarii blokuje ruch (fail closed) i wysyła alert, gdy zadanie próbuje połączyć się z celem niedozwolonym przez politykę. CRACI nie ocenia pakietów pod kątem złośliwego zachowania; aby blokować zainfekowane wersje przed instalacją, warto połączyć CRACI z narzędziem do oceny reputacji pakietów.
Prosimy umówić demo, aby zobaczyć zapis pakietów jednego z Państwa buildów.
