Programista sklonował publiczne repozytorium z GitHuba w izolowanym środowisku i zamiast odruchowo uruchomić npm install, najpierw zajrzał do plików konfiguracyjnych. To uchroniło go przed atakiem. W package.json znajdował się skrypt prepare, który npm wykonuje automatycznie po instalacji zależności i który pobierał złośliwy plik z domeny rest-icon-handler.store. Gdyby polecenie zostało uruchomione na codziennym laptopie, ukryty skrypt niemal natychmiast rozpocząłby pobieranie i wykonywanie payloadu w tle. Na pierwszy rzut oka może wyglądać to jak pojedyncza próba, ale podobny schemat krąży po świecie od miesięcy i trafia również do polskich deweloperów. Rok wcześniej analogiczna historia dotyczyła oferty pracy dla inżyniera Web3 za 3500 zł dziennie i została opisana na forum sekurak.pl. Mechanizm jest prosty, tani dla atakującego i może prowadzić do kradzieży portfeli kryptowalutowych, haseł zapisanych w przeglądarce oraz innych danych z komputera.
Co dokładnie znalazł użytkownik w repozytorium z LinkedIn
Roman, autor wpisu, opisuje, że dostał na LinkedIn wiadomość od rekruterki reprezentującej “small crypto startup”. Stanowisko brzmiało atrakcyjnie – lead engineer od zdalnego “broken proof-of-concept”, w którym trzeba ogarnąć kilka problemów z przestarzałymi modułami Node. Pretekst był dopracowany. Konto rekruterki miało doświadczenie, kontakty w branży, kilka rekomendacji. Repo na GitHubie też wyglądało normalnie – publiczne, z commitami, z wyglądającym sensownie README. Zamiast jednak uruchomić projekt na codziennym laptopie i wpisać npm install, Roman pobrał kod do izolowanego środowiska i najpierw zajrzał do plików konfiguracyjnych.
Tam siedział ukryty mechanizm. Plik package.json zawierał skrypt prepare, który npm wykonuje automatycznie zaraz po zaciągnięciu zależności. Skrypt wskazywał na plik app/test/index.js w repozytorium – dwustupięćdziesięcioliniowy moduł zamaskowany jako test suite, którego w rzeczywistości nikt by ręcznie nie odpalał. Kod pobierał payload spod adresu https://rest-icon-handler.store/icons/77 i przekazywał go do dynamicznego wykonania w procesie Node. Roman przerwał dalszą analizę zanim payload trafił na jego maszynę, dlatego nie wiemy dokładnie, co ten ładunek miał zrobić. Wiemy za to, jak wygląda mechanizm dostarczenia – publiczne repo z niewinną fasadą i jeden ukryty hook w skrypcie instalacyjnym.
Wystarczyłoby jedno polecenie powłoki, żeby skrypt z package.json zadziałał. Roman nie musiałby kliknąć żadnego linku, otworzyć załącznika z maila ani wpisać hasła do żadnego formularza. Wystarczyłoby, że zachowałby się tak, jak zachowuje się większość programistów na świecie codziennie, sto razy. Atakujący o tym wie i na tym buduje pułapkę.
Polski wątek – podobny schemat, kontakt z LinkedIn rok temu
Rok temu portal sekurak.pl opublikował chronologię ataku na polskiego programistę Daniela. Scenariusz różni się detalami, jednak szkielet jest bardzo zbliżony. Przejęte konto rekrutera o imieniu Brian napisało na LinkedIn z ofertą pracy nad platformą do stakingu Web3. Stawka: 120 dolarów na godzinę, czyli około 3500 zł dziennie. Stack: React, Solidity, Ethers.js. Praca w pełni zdalna. Po pytaniach o umiejętności rekruter przesłał Danielowi dokument z wymaganiami, a potem repozytorium opisane jako “wersja demo”.
W pliku server/utils/bootstrap.js Daniel znalazł użycie new Function.constructor, czyli najmniej rzucający się w oczy wzorzec dynamicznego wykonywania kodu w Node. Skrypt pobierał obfuskowany ładunek JavaScriptu z zewnętrznego URL-a i odpalał go z pełnymi uprawnieniami procesu. Celem ataku była kradzież portfeli kryptowalutowych ofiary oraz haseł zapisanych w Chrome. Daniel ograniczył ryzyko głównie dlatego, że nie uruchomił tego kodu na codziennej maszynie. Po pierwszym sygnale od niego prawdziwy Brian odpisał krótko: “Someone hacked into my account and started sending scam job posts”.
Dwie historie, podobny schemat. Atakujący korzysta z trzech elementów. Pierwszym jest przejęte albo wynajęte konto LinkedIn z wiarygodnym CV i kontaktami w branży. Drugim element to pretekst, który dla dewelopera wygląda normalnie – test rekrutacyjny z repo, nie żaden plik z maila. Trzecim jest ukrycie kodu malware w miejscu, którego programista nie czyta linijka po linijce, czyli w skrypcie instalacyjnym npm, w teście jednostkowym albo w pliku bootstrapującym serwer. Cała trójca razem przekonuje na tyle, że nawet ostrożna osoba potrafi odruchowo wpisać npm install. Z perspektywy atakującego koszt operacji jest niemal zerowy – jedno przejęte konto LinkedIn, jedno repozytorium na GitHubie i jeden serwer pośredniczący. Z perspektywy ofiary cena bywa kilkaset tysięcy złotych w portfelu kryptowalutowym albo dostęp do firmowych sekretów.
Dlaczego ten wektor działa lepiej niż klasyczny phishing
Phishing przez mail rozpoznaje dziś nawet umiarkowanie świadomy użytkownik. Dlatego atakujący przenieśli się o poziom wyżej, w miejsce, w którym deweloperzy mają opuszczoną gardę. LinkedIn ma dwie cechy, których zwykły mail nie ma. Pierwsza: deweloperzy aktywnie szukają ofert i sami wystawiają się jako “open to work”, co odwraca normalną psychologię obrony – to ofiara czeka na rozmowę, a nie atakujący próbuje ją zaczepić. Druga to kontakt biznesowy dający natychmiast pretekst do wymiany plików, repozytoriów i dokumentów technicznych. Mail od nieznanego nadawcy z załącznikiem ZIP budzi alarm. Repo na publicznym GitHubie podesłane przez rekruterkę z portfolio kontaktów w branży – już niekoniecznie.
Dodatkowo ekosystem npm wpisuje się w atak idealnie, mimo że sam jest narzędziem neutralnym. Skrypty preinstall, install i prepare w pliku package.json wykonują się automatycznie podczas instalacji zależności i mają domyślnie pełne uprawnienia procesu. Większość deweloperów nigdy ich nie czyta. Z kolei stack Web3 (React, Solidity, Ethers.js, hardhat) jest na tyle złożony, że plik z dziwnym importem w teście jednostkowym albo w module pomocniczym rzadko trafia pod lupę przy pierwszym przejściu po repo. To wszystko sprawia, że nie wystarczy “nie klikać linków od obcych” – trzeba aktywnie zmienić sposób, w jaki traktuje się kod z rekrutacji. Co więcej, presja stawki w okolicach 3500 zł dziennie albo 120 dolarów na godzinę robi swoje. Wiele osób w polskiej branży akurat teraz ma puste tygodnie w kalendarzu, a oferty dla juniorów IT pozostają w tym sezonie chude. Stawka wyższa niż średnia rynkowa w regionie potrafi uśpić nawet dobrze trenowaną ostrożność.
Pierwsza Misja AI · Kodożercy
Rozumiesz zagrożenia AI, gdy rozumiesz, jak naprawdę działa
Pierwsza Misja AI ma dedykowaną lekcję o ciemnej stronie technologii – halucynacje, deepfaki, manipulacja, social engineering wzmocniony przez modele. Zanim zaczniesz się bać, zacznij rozumieć.
Poznaj pełny program →

Pięć rzeczy, które polski deweloper powinien robić od jutra
npm install –ignore-scripts jako domyślna reguła dla obcego kodu
Najtańszą obroną jest zmiana domyślnego zachowania menedżera pakietów. Polecenie npm install --ignore-scripts instaluje zależności bez automatycznego uruchamiania skryptów preinstall, install i prepare. Dla codziennej pracy nad własnymi projektami zmiana niewiele kosztuje. Dla nowego repo z zewnątrz powinna być reguła nadrzędna. Można też dorzucić ten flag globalnie do konfiguracji npm w katalogu domowym i wyłączać go świadomie tylko dla zaufanych paczek. Oficjalna dokumentacja npm opisuje opcję ignore-scripts wprost – nie trzeba żadnych dodatkowych narzędzi.
Oddzielne środowisko na kod z rekrutacji
Kod z rekrutacji nigdy nie powinien trafiać na maszynę, na której trzymasz portfel kryptowalutowy, klucz SSH do firmowego serwera albo zalogowaną Chrome z hasłami. Najprostszą opcją jest oddzielna maszyna wirtualna, w której nie ma żadnych sekretów. Trochę bardziej zaawansowaną – konteneryzacja przez Docker albo systemy izolujące pokroju Firejaila na Linuksie. Koszt to kilkanaście minut konfiguracji i trochę dyskomfortu przy przełączaniu, w zamian dostajesz bardzo realny bezpiecznik.
Czytanie sekcji scripts w package.json przed instalacją
Praktyka brzmi nudno, ale ratuje. Przed npm install warto otworzyć plik package.json i przeczytać sekcję scripts. Jeśli widzisz tam coś, co nie wygląda jak vitest, jest, tsc, next build albo inny rozpoznawalny narzędziowiec, zatrzymaj się. Skrypty pobierające coś z internetu (curl, wget, fetch), wywołujące node z dziwną ścieżką do pliku albo wykonujące eval to czerwone flagi. Roman miał w package.json skrypt prepare wskazujący na test, którego nikt by ręcznie nie uruchamiał – już to powinno włączyć alarm.
Weryfikacja konta rekrutera w drugim kanale
Sprawdzaj historię konta rekrutera. LinkedIn pokazuje, kiedy konto zostało założone, ile osób ma w sieci, jakie aktywności prowadzi. Konto z piętnastoletnim stażem, ale aktywnością tylko z ostatniego tygodnia, jest podejrzane. Konto bez zdjęcia, z trzema kontaktami i ofertą za 3500 zł dziennie – jeszcze bardziej. Pomocnym ruchem bywa też napisanie do rekrutera bezpośrednio przez znajomego z branży albo na inny kanał (mail firmowy, X) z jednym zdaniem weryfikacyjnym. Daniel zatrzymał atak głównie dlatego, że nie uruchomił kodu na głównej maszynie, ale potwierdzenie od prawdziwego Briana, że konto zostało przejęte, przyszło dopiero później.
Jednostronicowa polityka onboardingowa w zespole
Piąta rzecz dotyczy zespołów. Jeśli prowadzisz w firmie politykę BYOD albo deweloperzy korzystają z własnego sprzętu do projektów klienta, dodaj do onboardingu sekcję “kod rekrutacyjny i testowy z zewnątrz”. Wystarczy jedna strona dokumentu, jedno polecenie --ignore-scripts w .npmrc zespołu i jasne wskazanie, gdzie odpalać tego typu kod. To poziom prewencji, którego nie zastąpi żaden zewnętrzny audyt cyberbezpieczeństwa. Taki krótki dokument to konkretny sygnał, że firma traktuje incydenty pokroju serii niedawnych ataków na pakiety npm jako realne ryzyko, a nie egzotykę z Twittera.
Podsumowanie
Atak przez fałszywą ofertę pracy na LinkedIn nie wymaga filmowych umiejętności hakerskich. Wystarczy przejęte konto rekrutera, repozytorium na GitHubie i jedno polecenie npm install wpisane przez programistę, który na moment stracił czujność. Dziś taki schemat uderza głównie w deweloperów Web3, JavaScriptu i Node.js, ale równie dobrze może zostać przeniesiony do ekosystemu Pythona, Rusta czy Go. Roman zatrzymał atak, bo zauważył skrypt prepare w package.json. Daniel, bo przeczytał bootstrap.js linijka po linijce, zanim cokolwiek uruchomił. Obie historie łączy jedna rzecz: nie potraktowali kodu przesłanego podczas rekrutacji jak własnego i zaufanego. To szczególnie ważne dziś, gdy polski rynek IT jest pod presją, ofert jest mniej, a stawka 3500 zł dziennie potrafi uśpić nawet dobrze wyrobioną ostrożność. Dlatego te pięć nawyków warto wprowadzić, zanim kolejna “lead engineer position” trafi również do naszej skrzynki. Atakujący już wiedzą, że ten schemat działa.
Newsletter · DevstockAcademy & Kodożercy
Bądź na bieżąco ze światem IT, AI i automatyzacji
Co wtorek: newsy z branży, praktyczne tipy i narzędzia które warto znać. Zero spamu.



