gik|iewicz

szukaj
npm 12 domyślnie blokuje skrypty instalacyjne pakietów – koniec ery ślepego zaufania

npm 12 domyślnie blokuje skrypty instalacyjne pakietów – koniec ery ślepego zaufania

npm 12 przynosi jedną z najpoważniejszych zmian bezpieczeństwa w historii tego menedżera pakietów: skrypty instalacyjne pakietów są domyślnie wyłączone, a rejestr przechodzi na model jawnej zgody. Zespół npm odwraca tym samym logikę, która obowiązywała przez ponad dekadę – to nie użytkownik musi teraz pilnować, co uruchamia się podczas npm install, tylko autor pakietu musi wyraźnie poprosić o zgodę.

TL;DR: npm 12 domyślnie blokuje wykonywanie skryptów lifecycle pakietów podczas instalacji, co zamyka najczęstszy wektor ataków na łańcuch dostaw JavaScript. Jednocześnie rejestr npm wprowadza model explicit trust – zaufanie do pakietu trzeba wyrazić wprost, a nie dziedziczyć automatycznie. Zmiana jest odpowiedzią na falę incydentów, takich jak kompromitacja pakietów npm w Red Hat Cloud Services.

Dlaczego npm 12 wyłącza skrypty instalacyjne domyślnie?

Skrypty instalacyjne, przede wszystkim postinstall oraz preinstall, od lat stanowiły najprostszą drogę do wykonania dowolnego kodu na maszynie dewelopera. Wystarczyło jedno npm install, by złośliwy pakiet uruchomił loader kryptokoparki albo skradł zmienne środowiskowe z tokenami CI. Zespół npm zdecydował się zatem odwrócić domyślne zachowanie – teraz skrypty po prostu się nie wykonują, chyba że użytkownik świadomie na to pozwoli.

To odwrócenie filozofii bezpieczeństwa z opt-in na opt-out. Dotychczas trzeba było znać flagę --ignore-scripts, żeby chronić swój projekt. Obecnie bezpieczeństwo jest stanem wyjściowym, a ryzyko wymaga aktywnej decyzji. Podobną drogę przeszły wcześniej inne ekosystemy, jednak npm przez długi czas bronił status quo ze względu na kompatybilność.

Czym groziły skrypty postinstall w ekosystemie npm?

Ataki na łańcuch dostaw npm regularnie wykorzystywały właśnie mechanizm skryptów lifecycle. Na przykład atak na łańcuch dostaw npm TanStack pokazał, jak przejęcie konta maintainera potrafi przenieść złośliwy kod do popularnej biblioteki. Z kolei Microsoft opisywał łagodzenie kompromitacji łańcucha dostaw npm Axios, gdzie kluczowe okazało się szybkie reagowanie po wykryciu podejrzanego pakietu.

Problem ma skalę systemową, ponieważ przeciętny projekt frontendowy ma setki zależności transytywnych. Nie sposób ręcznie zweryfikować każdą z nich, a każda paczka w drzewie mogła teoretycznie uruchomić kod w trakcie instalacji. Dlatego wyłączenie skryptów domyślnie chroni właśnie te miejsca, których deweloper nie kontroluje.

Jak działa model jawnej zgody w rejestrze npm?

Rejestr npm przechodzi z modelu niejawnego zaufania na explicit trust – koncepcję znaną z architektury zero trust. W praktyce oznacza to, że pobranie pakietu nie jest już równoznaczne z pozwoleniem na wykonanie jego kodu poza własnym modułem. Zaufanie trzeba przyznać świadomie, per pakiet lub per zakres.

Kluczowe elementy nowego podejścia:

  • Skrypty preinstall, install oraz postinstall nie uruchamiają się bez wyraźnej zgody użytkownika
  • npm wyświetla jasny komunikat, gdy pakiet żąda wykonania skryptu, zamiast wykonywać go po cichu
  • Zgoda może zostać zapisana w konfiguracji projektu, dzięki czemu zespoły utrzymują powtarzalność buildów
  • Zachowanie można nadal nadpisać flagą dla pakietów, które faktycznie wymagają skryptów (np. te kompilujące natywne binaria)
  • Zmiana dotyczy domyślnej konfiguracji, więc istniejące pipeline’y wymagają audytu przed migracją
  • Model obejmuje również zależności transytywne, nie tylko pakiety instalowane bezpośrednio
  • Komunikat o zablokowanym skrypcie zawiera nazwę pakietu oraz instrukcję nadania zgody
  • Zestaw zaufanych pakietów można wersjonować razem z repozytorium projektu

Warto sprawdzić, które zależności w projekcie faktycznie polegają na skryptach – najczęściej są to pakiety z natywnymi dodatkami, takie jak bindingi do bazy danych czy biblioteki kryptograficzne.

AspektStare zachowanie (npm 11)Nowe zachowanie (npm 12)
Skrypty lifecycleWykonywane automatycznieBlokowane bez zgody
OchronaOpcjonalna flaga --ignore-scriptsStan domyślny
Zaufanie do pakietuNiejawne, wynika z instalacjiJawne, per pakiet lub zakres
KomunikacjaSkrypt działa po cichuWidoczny komunikat o żądaniu
Migracja pipeline’ówNie wymaganaWymaga audytu zależności

Co muszą zmienić maintainerzy pakietów?

Autorzy pakietów odczują zmianę najbardziej tam, gdzie skrypty instalacyjne pełniły realną funkcję – kompilacja natywnych modułów, pobieranie binariów albo generowanie plików. Takie pakiety będą musiały wyraźnie komunikować użytkownikom, że wymagają zgody na wykonanie skryptu. Ponadto dokumentacja i komunikaty błędów stają się ważniejsze niż dotychczas.

Zmiana wymusza też przemyślenie architektury pakietów. Pakiety, które potrafią działać bez skryptów – na przykład przez dystrybucję prekompilowanych artefaktów dla popularnych platform – zyskują przewagę w nowym modelu. Z kolei te oparte na kompilacji w locie będą wymagały od użytkowników dodatkowego kroku. Najważniejsze jest, aby maintainerzy nie próbowali obchodzić nowego mechanizmu, tylko dostosować się do niego.

Jak zmiana wpłynie na pipeline’y CI/CD i automatyczne buildy?

Największym wyzwaniem operacyjnym npm 12 jest migracja istniejących pipeline’ów, ponieważ wiele z nich po cichu polega na skryptach instalacyjnych zależności. Build, który dotychczas kończył się sukcesem, może teraz produkować artefakty bez skompilowanych natywnych modułów – a więc cicho uszkodzone. Dlatego zespoły DevOps powinny potraktować aktualizację jako zmianę łamiącą zgodność, a nie rutynową aktualizację minorową.

Praktyczny plan migracji może wyglądać tak:

  • Audyt zależności pod kątem pakietów korzystających ze skryptów postinstall i preinstall
  • Ustalenie, które z nich faktycznie potrzebują wykonywania kodu, a które tylko go wywołują z przyzwyczajenia
  • Zapisanie jawnej zgody dla niezbędnych pakietów w konfiguracji projektu, aby buildy były powtarzalne
  • Dodanie kroku weryfikacji artefaktów po instalacji, na przykład testu ładowania natywnych modułów
  • Komunikacja zmiany w zespole, żeby nikt nie „naprawiał” builda globalnym wyłączeniem ochrony
  • Zaplanowanie aktualizacji na okres po zamrożeniu feature’ów, nie w trakcie releasu

Co więcej, podejście to wpisuje się w szerszy trend zabezpieczania pipeline’ów, opisywany choćby w analizie jak GitHub zabezpiecza agentowe przepływy pracy w nowoczesnych systemach CI/CD. Tam również kluczowym założeniem jest jawność uprawnień zamiast domyślnego zaufania.

Czy wyłączone skrypty zepsują popularne pakiety?

Pakiety z natywnymi dodatkami odczują zmianę najszybciej, ponieważ ich instalacja wymaga kompilacji lub pobrania prekompilowanych binariów. Jednakże większość bibliotek czysto JavaScriptowych nie używa skryptów lifecycle wcale – dla nich aktualizacja będzie obojętna. Problem dotyczy wąskiej, choć ważnej grupy: bindingów baz danych, bibliotek kryptograficznych oraz narzędzi wymagających binariów systemowych.

Kluczowe kategorie pakietów, które wymagają uwagi po migracji:

  • Pakiety kompilujące kod natywny przez node-gyp podczas postinstall
  • Narzędzia pobierające własne binaria, na przykład sterowniki przeglądarek do testów E2E
  • Biblioteki kryptograficzne z bindingami do OpenSSL
  • Pakiety generujące kod lub zasoby w trakcie instalacji
  • Wewnętrzne paczki firmowe, których dokumentacja nie przewidziała nowego modelu zgody
  • Narzędzia CLI instalujące dodatkowe komponenty po instalacji

Maintainerzy mają zatem dwie drogi: wyraźnie poprosić o zgodę na skrypt albo przejść na dystrybucję prekompilowanych artefaktów. Ta druga opcja jest zgodna z kierunkiem całego ekosystemu.

Jak jawne zaufanie zmienia ekonomię ataków na łańcuch dostaw?

Model explicit trust podnosi koszt ataku zamiast naprawiać skutki. Dotychczas przejęcie konta maintainera popularnej paczki dawało atakującemu natychmiastowe wykonanie kodu na maszynach tysięcy deweloperów. Obecnie taki kod napotyka barierę w postaci świadomej zgody użytkownika, co drastycznie zawęża zasięg potencjalnej kompromitacji.

Warto przypomnieć, że mechanizm skryptów lifecycle był wykorzystywany w realnych incydentach – atak na łańcuch dostaw npm TanStack pokazał, jak szybko złośliwa wersja paczki rozchodzi się po drzewie zależności. Z kolei łagodzenie kompromitacji łańcucha dostaw npm Axios opisywane przez Microsoft pokazało, że nawet błyskawiczna reakcja nie zastąpi profilaktyki po stronie klienta. npm 12 przenosi tę profilaktykę z rąk zespołów bezpieczeństwa do domyślnej konfiguracji narzędzia.

Więcej kontekstu o wydaniu znajduje się w oryginalnym artykule InfoQ o npm 12.

Często zadawane pytania

Czy mogę wrócić do starego zachowania npm z automatycznymi skryptami?

Tak, konfiguracja pozwala jawnie zezwolić na wykonywanie skryptów – globalnie lub per pakiet – jednak npm 12 czyni blokadę domyślną. Rekomendacja: zezwalaj tylko na listę zweryfikowanych pakietów, a nie globalnie, bo to usuwa całą ochronę.

Jak sprawdzić, które pakiety w moim projekcie używają skryptów instalacyjnych?

Należy przejrzeć pole scripts w plikach package.json wszystkich zależności transytywnych lub użyć polecenia instalacji z trybem interaktywnym, które w npm 12 raportuje pominięte skrypty. Zacznij od zależności natywnych – to one najczęściej wymagają postinstall.

Czy npm 12 łamie istniejące projekty po aktualizacji?

Buildy zależne od skryptów lifecycle przestaną działać poprawnie, ponieważ skrypty nie wykonają się bez zgody – szczególnie pakiety kompilujące natywne moduły. Przed migracją wykonaj audyt zależności i zapisz zgodę dla niezbędnych paczek w konfiguracji projektu.

Dlaczego rejestr npm przechodzi na model explicit trust?

Ponieważ niejawnego zaufania nie da się skalować przy setkach zależności transytywnych w typowym projekcie, a skrypty lifecycle były wektorem realnych ataków, jak kompromitacja pakietów npm w Red Hat Cloud Services. Explicit trust przenosi decyzję o wykonaniu kodu do świadomego wyboru użytkownika.

Podsumowanie

npm 12 to zmiana filozoficzna bardziej niż techniczna. Po pierwsze, bezpieczeństwo przestaje być opcją, którą trzeba włączyć – blokada skryptów lifecycle jest stanem domyślnym. Po drugie, rejestr przechodzi na explicit trust, więc zaufanie do pakietu trzeba wyrazić wprost, a nie dziedziczyć z faktu instalacji. Po trzecie, maintainerzy pakietów natywnych muszą zaktualizować dokumentację i komunikację, bo ich użytkownicy napotkają nowy krok instalacji. Po czwarte, zespoły DevOps powinny potraktować migrację jako zmianę łamiącą zgodność i przeprowadzić audyt pipeline’ów.

Jeśli utrzymujesz projekt npm, zacznij od audytu zależności już dziś – sprawdź, które paczki korzystają ze skryptów postinstall, i zdecyduj świadomie, którym z nich ufasz. Warto też śledzić reakcje ekosystemu na oryginalne omówienie npm 12 w InfoQ oraz analizy incydentów na łańcuchu dostaw publikowane na tym blogu.

Autor: Grzegorz Kikiewicz – blog gikiewicz.eu, teksty o technologii, AI i programowaniu.