Powrót do bloga
21 września 2026

Anatomia Miasmy: jak jedno przejęte konto npm zamieniło się w atak na łańcuch dostaw obejmujący wiele ekosystemów

Viktor Bulanek
Founder & CTO, Penetrify
MSc IT Security · 20+ years in security · 4x Ex-CTO

W czerwcu 2026 roku badacze odkryli kolejną odsłonę rodziny malware Mini Shai-Hulud, nazwaną Miasma. W odróżnieniu od klasycznego złośliwego oprogramowania w npm, które uderza w garść pakietów, ta kampania pokazała, jak przejęcie jednego elementu infrastruktury deweloperskiej może błyskawicznie rozlać się na wiele ekosystemów: npm, GitHub Actions, a nawet moduły Go.

Atakujący nie celowali w użytkowników końcowych. Celowali w publikujących, w pipeline’y CI/CD oraz w mechanizmy zaufania, które pozwalają oprogramowaniu przejść z laptopa dewelopera na produkcję bez ponownej weryfikacji po drodze. Dla żadnego z tych elementów nie istnieje CVE, ponieważ dotknięty kod nigdy nie był wadliwy. Punktem wejścia było skradzione poświadczenie, nie podatność.

Jedną rzecz warto zaznaczyć na wstępie: 12 maja 2026 roku grupa, która pierwotnie stała za tą rodziną robaków, TeamPCP, publicznie opublikowała na GitHubie pełny kod źródłowy Shai-Hulud/Mini Shai-Hulud na licencji MIT. To zmienia sposób, w jaki należy czytać tę historię. Cztery kolejne fale korzystają z tego samego zestawu narzędzi i technik, ale wspólne narzędzia nie oznaczają już wspólnego operatora. Gdy framework jest publiczny, każdy technicznie kompetentny aktor może przeprowadzić kampanię wyglądającą identycznie jak działania samego TeamPCP. To, co następuje, należy czytać jako pokrywające się taktyki, techniki i procedury (TTP), a nie jako potwierdzoną atrybucję do jednej grupy.

Dotknięte pakiety (npm i Go)

PakietZainfekowana wersja
hexo-deployer-wrangler1.0.4
hexo-shoka-swiper0.1.10
leo-auth4.0.6
leo-aws2.0.4
leo-cache1.0.2
leo-cdk-lib0.0.2
leo-cli3.0.3
leo-config1.1.1
leo-connector-elasticsearch2.0.6
leo-connector-mongo3.0.8
leo-connector-mysql3.0.3
leo-connector-oracle2.0.1
leo-connector-redshift3.0.6
leo-cron2.0.2
leo-logger1.0.8
leo-sdk6.0.19
leo-streams2.0.1
prism-silq1.0.1
rstreams-metrics2.0.2
rstreams-shard-util1.0.1
serverless-convention2.0.4
serverless-leo3.0.14
solo-nav1.0.1
github.com/verana-labs/verana-blockchain (Go)v0.10.1-dev.20

Socket kontynuuje dochodzenie; nowe ustalenia są na bieżąco dodawane na ich stronie śledzącej kampanię Miasma.

Jeśli w Twoim drzewie zależności znajduje się którykolwiek z tych pakietów w wymienionych wersjach (lub pakiety z wcześniejszych fal tej samej kampanii z początku czerwca), traktuj środowisko jako potencjalnie skompromitowane i wykonaj natychmiastowe kroki naprawcze opisane poniżej.

Oś czasu

24 czerwca 2026
   ↓
Przejęcie konta maintainera LeoPlatform
   ↓
Publikacja 23 złośliwych wersji pakietów (w odstępie sekund)
   ↓
Payload w binding.gyp wykonuje się podczas instalacji
   ↓
Kradzież poświadczeń deweloperów i CI/CD
   ↓
Wdrożenie workflow GitHub Actions („Run Copilot”)
   ↓
Eksfiltracja skradzionych sekretów do publicznych repozytoriów GitHub
   ↓
Przejęcie modułu Go (Verana Blockchain) pokrewną techniką
   ↓
Badacze z Socket, Wiz, Sonatype i innych firm identyfikują i ujawniają kampanię

To już czwarta fala tej samej rodziny kampanii w niecały miesiąc: najpierw pojawiła się 1 czerwca w przestrzeni nazw npm należącej do Red Hata, 3 czerwca nastąpiła fala 57 pakietów, a 24–25 czerwca ta fala LeoPlatform/Go. Jak wspomniano wyżej, TeamPCP otworzyło kod bazowego frameworka robaka 12 maja, więc określenie „ta sama rodzina kampanii” odzwierciedla wspólne TTP tych fal, a nie potwierdzony dowód, że za wszystkimi czterema stoi jeden operator.

Co tak naprawdę się wydarzyło

  • Maintainerowi ekosystemu npm LeoPlatform skradziono poświadczenia do konta lub token npm, najprawdopodobniej za pomocą infostealera.
  • Atakujący wykorzystał ten skradziony dostęp do publikacji strojanizowanych wersji pakietów, które maintainer już legalnie posiadał. Nic nie trzeba było „hakować” w tradycyjnym sensie, ponieważ atakujący dysponował już zaufaną tożsamością publikującego.
  • Każdy, kto zainstalował jedną z tych wersji, uruchomił ukryty payload, który kradł jego własne poświadczenia.
  • Część skradzionych poświadczeń należała do środowisk GitHub Actions, co pozwoliło atakującemu podłożyć złośliwy workflow i wykradać sekrety CI/CD bezpośrednio z runnera.
  • Niezależnie od tego przejęto moduł Go za pomocą pokrewnej, choć odrębnej techniki, co pokazuje, że kampania nie jest przywiązana do jednego języka ani menedżera pakietów.
  • Całość wykryto dlatego, że kilka niezależnych zespołów badawczych obserwowało dokładnie ten wzorzec znany z wcześniejszych fal tej samej kampanii.

Analiza techniczna

Etap 1: przejęty maintainer

Konto npm czirker, powiązane z LeoPlatform, zostało przejęte, najprawdopodobniej przez wyciek poświadczeń lub token wykradziony przez infostealera działającego już na maszynie maintainera.

Etap 2: publikacja złośliwych pakietów

Za pomocą skradzionego tokenu atakujący opublikował 23 złośliwe wersje pakietów w ekosystemach LeoPlatform i RStreams w oknie sześciu sekund. Taka szybkość wskazuje na skryptowane, zautomatyzowane narzędzie do publikacji, a nie na człowieka ręcznie wgrywającego pliki.

Najbardziej konkretny szczegół całej kampanii kryje się właśnie w tym etapie: atakujący nie tylko opublikował złośliwe wersje, ale też przestawił dist-tag latest pakietu leo-sdk na zatrutą wersję 6.0.19, podczas gdy czyste wydanie 7.1.21 pozostało dostępne wyłącznie pod trudniejszym do znalezienia tagiem awsv3. W praktyce oznacza to, że każde środowisko, które wykonało npm install leo-sdk bez jawnego przypięcia wersji semver, było aktywnie kierowane w stronę robaka, mimo że czyste wydanie istniało przez cały ten czas. Praktyczny wniosek jest brutalnie prosty: to przypięcie wersji, a nie tagu, faktycznie decydowało o tym, czy instalacja była czysta, czy zakończyła się kompromitacją.

Etap 3: wykonanie podczas instalacji

To jest sprytna część. Większość złośliwych pakietów npm zostaje wykryta, ponieważ skanery obserwują pola skryptów preinstall i postinstall w package.json. Miasma całkowicie je omija i zamiast tego dodaje plik binding.gyp, czyli konfigurację, której Node.js używa do kompilowania modułów natywnych podczas instalacji. Ponieważ binding.gyp jest zupełnie normalnym elementem budowania natywnych dodatków, uruchamia kod podczas npm install, nie dotykając pól, na które patrzy większość narzędzi.

Ta technika miała dwa realne środki zaradcze i warto być precyzyjnym co do obu. Pierwszy istniał już wcześniej: npm install --ignore-scripts faktycznie wyłącza niejawny hook node-gyp rebuild, który wyzwala plik binding.gyp, jak podaje własna dokumentacja npm. Samo w sobie nie jest to pełna obrona, ponieważ złośliwy pakiet nadal może zostać pobrany i rozpakowany, a późniejsze przebudowanie bez tej flagi wciąż może go uruchomić, ale ta konkretna ścieżka wykonania zostaje zablokowana. Drugi środek zaradczy pojawił się o trzy tygodnie za późno: npm v12, wydany w lipcu 2026 roku, domyślnie zamyka ten wektor, traktując niejawny node-gyp rebuild wyzwalany przez binding.gyp dokładnie tak jak każdy inny skrypt instalacyjny, czyli blokując go, o ile pakiet nie znajduje się jawnie na liście dozwolonych. Technika „Phantom Gyp” użyta w Miasmie była w istocie bezpośrednim wykorzystaniem dokładnie tej luki, którą npm v12 miał zamknąć.

Etap 4: zbieranie sekretów

Po uruchomieniu payload zbiera szeroki zestaw poświadczeń, w tym:

  • tokeny npm i PyPI
  • tokeny GitHub Personal Access Token i tokeny GitHub OIDC
  • poświadczenia AWS, Azure i GCP
  • klucze SSH
  • ścieżki konfiguracji i poświadczeń Claude Code, Cursora i innych asystentów AI do programowania
  • klucze API OpenAI
  • tokeny Slack i Twilio, pliki uwierzytelniania Dockera, konfiguracje Kubernetes, dane Vault oraz historię powłoki

Etap 5: nadużycie GitHuba

Malware podrzuca workflow GitHub Actions (widziany pod nazwą „Run Copilot”), który podczas wykonywania wyciąga sekrety bezpośrednio z pamięci runnera CI. Skradzione dane są szyfrowane i wypychane do nowo utworzonych publicznych repozytoriów GitHub pełniących rolę skrzynek kontaktowych; w chwili pisania tego tekstu ponad 550 repozytoriów nosi znacznik eksfiltracji tej kampanii. W powiązanym incydencie tego samego dnia atakujący wykonał force-push złośliwego commita do szeroko używanej akcji GitHub Action i przekierował na niego tagi wersji, co oznacza, że każdy workflow uruchomiony po tym momencie względem tych tagów wykonywał payload atakującego bezpośrednio w runnerze GitHub Actions.

Etap 6: propagacja

Przejęcie maszyny dewelopera
   ↓
Kradzież poświadczeń GitHub dewelopera
   ↓
Zebranie sekretów pipeline’u CI/CD z runnera
   ↓
Użycie skradzionego tokenu npm do publikacji kolejnych złośliwych pakietów
   ↓
Kolejni deweloperzy instalują zainfekowane pakiety
   ↓
(powtórka)

Każda udana kompromitacja staje się trampoliną dla następnej. To właśnie czyni z tego samopropagującą się kampanię, a nie pojedynczy incydent.

Dlaczego ten atak jest inny

Dawne ataki na łańcuch dostawMiasma
Pojedynczy pakietCały ekosystem
Kradzież kryptowalutSzeroka kradzież poświadczeń
Jeden językWiele języków (npm, teraz Go)
Jeden maintainerInfrastruktura deweloperska jako całość
Statyczny malwareSamopropagujący się, ewoluujący między falami

Wcześniejsze incydenty w łańcuchu dostaw npm były w większości szybkimi skokami: przejąć pakiet, wykraść klucze do portfeli lub tokeny sesji, iść dalej. Miasma zachowuje się raczej jak robak z modelem biznesowym. Każdą udaną infekcję traktuje jako paliwo dla następnej fali i, jak widać, wciąż ewoluuje po ponad trzech tygodniach i czterech odrębnych falach.

Dlaczego GitHub Actions stał się celem

GitHub Actions zajmuje wyjątkowo cenne miejsce w łańcuchu dostaw oprogramowania: przechowuje tokeny OIDC używane do zaufanej, bezpoświadczeniowej publikacji u dostawców chmury i w rejestrach, trzyma długowieczne sekrety CI i z założenia działa w sposób zautomatyzowany i bezobsługowy. Przejęcie runnera CI nie odsłania tylko laptopa jednego dewelopera, odsłania każdy pipeline wdrożeniowy, każdą publikację do rejestru i każde poświadczenie chmurowe, którego ten workflow dotyka. To znacznie większy promień rażenia niż lokalne środowisko pojedynczego dewelopera i właśnie dlatego kampania zbudowała dedykowane narzędzia wymierzone konkretnie w ten cel.

Dlaczego ekspansja na Go ma znaczenie

Większość relacji o tej kampanii skupiała się na npm, bo tam znajduje się większość pakietów. Ale przejęcie modułu Go jest prawdopodobnie najważniejszym szczegółem całej historii i zarazem tym, który większość opracowań pomija.

Próbka Go nie używa binding.gyp. Nie opiera się w ogóle na mechanizmach rozwiązywania modułów ani budowania w Go. Zamiast tego deweloper, który po prostu sklonuje repozytorium albo otworzy je w zaufanym IDE lub asystencie AI do programowania, może uruchomić payload wyłącznie przez konfigurację projektu.

To mówi coś istotnego: atakujących nie interesuje w gruncie rzeczy JavaScript, npm ani mechanika żadnego konkretnego menedżera pakietów. Interesują ich deweloperzy, wszędzie tam, gdzie deweloperzy ufają zautomatyzowanym narzędziom, czy będzie to skrypt instalacyjny, krok budowania, czy IDE otwierające folder. Język nie ma znaczenia. Poświadczenia deweloperów mają.

Szersza lekcja

Stare myślenie:

Skanujemy zależności
   ↓
Łatamy znane CVE
   ↓
Jesteśmy bezpieczni

Współczesna rzeczywistość:

Deweloper zostaje przejęty
   ↓
CI/CD zostaje przejęte
   ↓
Legalny pakiet zostaje opublikowany ze złośliwym kodem
   ↓
Każdy konsument w dół łańcucha go instaluje
   ↓
Na żadnym etapie nie jest potrzebne CVE

Skanowanie zależności odpowiada na pytanie „czy ten kod ma znaną wadę”. Miasma odsłania zupełnie inne pytanie, na które większość programów bezpieczeństwa w ogóle nie jest przygotowana odpowiedzieć: „czy ufam ludziom i zautomatyzowanym systemom, którym wolno publikować kod do mojego środowiska, teraz, dzisiaj”.

Możliwości detekcji

Zamiast pytać „jak to zatrzymać”, bardziej użyteczne jest pytanie, gdzie obrońca realistycznie miał szansę zauważyć, że coś jest nie tak. W tym miejscu większość relacji o Miasmie się urywa, więc warto omówić to dokładniej.

Tempo publikacji. 23 wersje pakietów opublikowane w oknie sześciu sekund to nie jest coś, co robi człowiek. Każda organizacja utrzymująca własne mirror rejestru npm albo obserwująca zdarzenia publicznego rejestru dla pakietów, od których zależy, mogłaby automatycznie flagować takie serie publikacji. Pojedyncze podbicie wersji jest normalne. Dwa tuziny w kilka sekund to sygnał sam w sobie, niezależnie od tego, co pakiet faktycznie zawiera.

Zmiany dist-tagów bez podbicia wersji, o które nikt nie prosił. Przejęcie leo-sdk zadziałało właśnie dlatego, że latest przeskoczył na niższy, starszy na pierwszy rzut oka numer wersji (6.0.19), podczas gdy nowsze czyste wydanie (7.1.21) siedziało pod innym tagiem. Każde narzędzie porównujące w czasie dist-tagi pakietu, a nie tylko listę jego wersji, wychwyciłoby, że latest wskazuje wstecz w sekwencji wersji, co niemal nigdy nie jest legalnym zachowaniem.

Artefakty budowania niezgodne ze źródłem. Zainfekowany pakiet leo-sdk rozrósł się z 10 KB index.js w czystej wersji do 5 MB index.js w wersji zatrutej, z plikiem binding.gyp, który nie istnieje nigdzie w faktycznym repozytorium GitHub projektu. Porównanie tego, co jest opublikowane w rejestrze, z tym, co jest zacommitowane w systemie kontroli wersji, pakiet po pakiecie, ujawniłoby to natychmiast. Większość organizacji w ogóle nie wykonuje takiego porównania.

Nieoczekiwane natywne buildy w pakietach czysto JavaScriptowych. leo-sdk to SDK w JavaScripcie bez żadnej uzasadnionej potrzeby kompilacji natywnej. Plik binding.gyp pojawiający się w pakiecie, który nigdy wcześniej go nie zawierał albo nie ma wiarygodnego powodu, by go potrzebować, jest sam w sobie silnym sygnałem anomalii, na długo zanim ktokolwiek sprawdzi, co ten plik faktycznie robi.

Nowe pliki workflow pojawiające się bez odpowiadającego im pull requesta. Plik workflow o nazwie „Run Copilot” pojawiający się w .github/workflows/ bez powiązanego PR, bez review i bez autora, którego ktokolwiek rozpoznaje, to dokładnie ten rodzaj zmiany, do wychwytywania którego istnieją ochrona gałęzi i wymagane review, o ile faktycznie są egzekwowane konkretnie dla plików workflow.

Ruch wychodzący do świeżo utworzonych repozytoriów GitHub. Ponieważ eksfiltracja opierała się na nowo utworzonych publicznych repozytoriach pełniących rolę skrzynek kontaktowych, runnery CI lub maszyny deweloperów wykonujące wychodzące wywołania API tworzące nowe repozytoria albo wypychające do repozytoriów, które godzinę wcześniej nie istniały, to wykrywalny i dość nietypowy wzorzec zachowania.

Użycie OIDC i tokenów poza normalnym wzorcem. Token GitHub OIDC lub token publikacji npm użyty z zakresu IP, workflow lub okna czasowego, z którego nigdy wcześniej nie był używany, to klasyczny sygnał anomalii tożsamości, do którego wychwycenia nie trzeba wiedzieć nic o tej konkretnej kampanii.

Żadna z tych metod nie wymaga bazy CVE ani wcześniejszej wiedzy konkretnie o Miasmie. Wymagają one faktycznego obserwowania, jak zaufanie, zachowania publikacyjne i tożsamości przepływają przez Twoje środowisko w czasie, a nie tylko tego, co jest wymienione w pliku manifestu w jednym punkcie czasu.

Co powinny zrobić organizacje

Natychmiast

  • Zrotuj poświadczenia w każdym środowisku, które zainstalowało dotknięty pakiet, ale dopiero po usunięciu złośliwej wersji i potwierdzeniu, że mechanizmy persystencji zostały usunięte, nie wcześniej.
  • Zaktualizuj lub usuń dotknięte wersje pakietów w lockfile’ach, cache’ach, kontenerach i runnerach CI, nie tylko w manifeście najwyższego poziomu.
  • Unieważnij i wydaj ponownie wszystkie tokeny GitHub, konfiguracje OIDC i tokeny npm, które mogły zostać ujawnione.
  • Przypinaj akcje GitHub Actions do hasha commita zamiast do zmiennych tagów wersji.

Średnioterminowo

  • Wprowadź listy dozwolonych pakietów dla tego, co jest dopuszczone w CI/CD i buildach produkcyjnych.
  • Wymagaj provenance i podpisywania pakietów tam, gdzie są dostępne.
  • Utrzymuj prawdziwy Software Bill of Materials (SBOM), a nie jednorazową migawkę.
  • Dodaj okno karencji, zanim nowo opublikowane wersje pakietów zostaną uznane za zaufane w buildzie.

Długoterminowo

  • Ciągła walidacja tego, co faktycznie działa w Twoim środowisku, zamiast okresowych audytów.
  • Monitorowanie repozytoriów i CI/CD pod kątem nieoczekiwanych zmian w workflow lub zachowaniach publikacyjnych.
  • Bieżąca analiza ścieżek ataku: nie tylko „czy ten pakiet jest podatny”, ale „do czego ktoś mógłby dotrzeć, gdyby ten pakiet był złośliwy”.
  • Monitorowanie tożsamości deweloperów, ponieważ w tej kampanii to tożsamość była faktyczną powierzchnią ataku.

Dlaczego to ma znaczenie dla Penetrify

Miasma nie była problemem npm. Była problemem tożsamości. Problemem CI/CD. Problemem zaufania. Problemem ścieżki ataku.

Atakujący nie odkryli podatności. Odkryli ścieżkę od stacji roboczej jednego dewelopera do tysięcy systemów w dół łańcucha, a ta ścieżka istniała w całości w relacjach, których większość programów bezpieczeństwa nigdy faktycznie nie monitoruje: którzy ludzie mogą publikować kod, które tokeny mogą uruchomić wdrożenie, które workflow działają bezobsługowo z realnym dostępem do produkcji.

npm v12 zamyka konkretny wektor wykonania, na którym opierała się Miasma. Nie robi nic z leżącym pod spodem problemem tożsamości. Jak ujął to Moshe Siman Tov Bustan z OX Security w relacji o wdrożeniu npm v12: gdy atakujący kontroluje poświadczenia legalnego maintainera, blokowanie skryptów instalacyjnych nie pomaga, ponieważ złośliwy kod trafia do użytkowników jako zaufane, podpisane wydanie.

To cała teza tego artykułu, wyrażona prościej, niż sami potrafilibyśmy to zrobić. Współczesne bezpieczeństwo łańcucha dostaw oprogramowania musi wyjść poza skanowanie zależności i zmierzać w stronę ciągłego rozumienia, jak relacje zaufania ewoluują między repozytoriami, pipeline’ami budowania, tożsamościami i środowiskami produkcyjnymi, a nie tylko tego, jakie CVE widnieją w pliku manifestu.

Źródła

Frequently Asked Questions

Jakie typy podatności wykrywa Penetrify?

Penetrify wykrywa wszystkie kategorie podatności OWASP Top 10, w tym SQL injection, XSS, CSRF, IDOR, złamaną autentykację, błędne konfiguracje zabezpieczeń i ujawnianie wrażliwych danych. Testuje również bezpieczeństwo API, zarządzanie sesją oraz typowe błędy konfiguracji w Supabase, Firebase i Bubble.

Jak długo trwa test penetracyjny AI?

Szybkie skanowanie kończy się w 15–30 minut. Standardowe skanowanie trwa 1–2 godziny z szerszym zakresem. Głęboke skanowanie może trwać kilka godzin dla złożonych aplikacji.

Co zawiera raport Penetrify?

Każdy raport zawiera podsumowanie wykonawcze, ogólny wynik bezpieczeństwa, znaleziska sklasyfikowane według wagi (Krytyczne, Wysokie, Średnie, Niskie), szczegółowe kroki reprodukcji oraz konkretne wskazówki dotyczące naprawy napisane dla deweloperów – nie dla specjalistów ds. zgodności.

Related articles

Testowanie Zgodności z Wieloma Standardami: Jedno Zlecenie, Wielu Audytorów
Podlegasz jednocześnie wymogom SOC 2, PCI DSS i HIPAA? Dowiedz się, jak uniknąć redundantnych testów i spełnić oczekiwania wszystkich audytorów w ramach jednego programu.
Co to jest Pen Test? Przewodnik krok po kroku po tym, jak to działa
Czy Twoja aplikacja internetowa jest naprawdę bezpieczna? Myśl o pojedynczej, ukrytej luki prowadzącej do katastrofalnego wycieku danych wystarczy, aby każdy założyciel nie mógł spać w nocy. Wiesz, że musisz podjąć działania, ale świat cyberbezpieczeństwa może wydawać się labiryntem.
Skanowanie zgodności z PCI DSS: Przewodnik po automatyzacji bezpieczeństwa (2026)
14 marca 2025 roku jeden z największych sprzedawców detalicznych odkrył, że pojedyncza, błędnie skonfigurowana reguła zapory sieciowej podczas piątkowego wdrożenia unieważniła trzy miesiące przygotowań do zgodności w mniej niż sześć minut. Prawdopodobnie wiesz już, że tradycyjne, kwartalne skanowanie zgodności z PCI DSS przypomina bardziej sprawdzanie prędkościomierza…

Explore more