Zamieńmy Twój pomysł w przejrzysty plan

Typ projektu

Dziękujemy!

Odpowiemy w ciągu 24 godzin.

“Nasz zespół potrafi przekształcić każdy pomysł w rozwijający się produkt”

Taras Gopko

CEO i założyciel Appricotsoft

zależności przechodnie – co zawiera kompilacja

Zależności przechodnie: dlaczego nikt nie wie, co zawiera kompilacja

Zależności przechodnie to pakiety, od których zależą twoje pakiety: dodajesz kilka bibliotek, a każda z nich pociąga za sobą kolejne, które z kolei pociągają za sobą kolejne. Większość tego drzewa jest nieszkodliwa, a sama liczba niewiele mówi. Prawdziwym problemem nie jest to, ile masz pośrednich zależności, ale to, że nikt z zespołu ich nie widzi i nikt ich nie wybrał.

Dlaczego zależności przechodnie stanowią problem widoczności, a nie problem liczby

Otwarte pakiet.json W typowym projekcie frontendowym widzisz krótką, przejrzystą listę. To są twoje bezpośrednie zależności – te, które ktoś z zespołu postanowił dodać. Następnie otwórz plik lockfile, a lista zamieni się w dokument, po którym będziesz przewijać przez długi czas. Ta różnica to drzewo zależności: każdy pakiet, którego potrzebują twoje bezpośrednie zależności, oraz wszystko, czego z kolei potrzebują te pakiety.

Pierwszą reakcją jest zazwyczaj niepokój, który w większości przypadków jest nieuzasadniony. Ekosystem npm opiera się na niewielkich pakietach, które realizują jedną funkcję. Narzędzie do kompilacji, narzędzie do uruchamiania testów oraz framework interfejsu użytkownika będą korzystać z parserów, narzędzi do obsługi ścieżek, polyfillów i funkcji pomocniczych. Duża liczba zależności npm wynika z samej konstrukcji ekosystemu i nie jest oznaką, że coś poszło nie tak.

To, co nie jest w porządku i na co warto zwrócić uwagę, to fakt, że żadna z tych pośrednich zależności nie została poddana procesowi decyzyjnemu. Nikt z zespołu nie ocenił autora, historii utrzymania ani tego, co robi pakiet po zainstalowaniu. Każdy z nich działa z takimi samymi uprawnieniami jak Twój własny kod – w Twojej kompilacji, na komputerach Twoich programistów oraz w środowisku CI. Pytanie nie brzmi więc: ’dlaczego mamy ich tak wiele?“, ale: ”które z nich odrzucilibyśmy, gdyby ktoś o to zapytał?“.”

To nowe spojrzenie pozwala również zrozumieć, czym tak naprawdę jest nadmierne rozrost zależności. Nie chodzi tu o duże drzewo. Chodzi o tę część drzewa, która zwiększa ryzyko lub obciążenie, nie zapewniając przy tym niczego, co jest ci potrzebne: duplikaty, porzucony kod oraz pakiety, które podczas instalacji wykonują więcej czynności, niż wymaga tego ich zadanie.

Jak powstaje drzewo zależności

Aby zrozumieć strukturę drzewa, warto wiedzieć, w jaki sposób menedżer pakietów je tworzy.

Rozróżnianie wersji

Każdy pakiet.json określa zakresy, a nie konkretne wersje. ^4.2.0 oznacza “dowolną wersję 4.x, począwszy od 4.2.0”. Podczas instalacji menedżer pakietów przegląda graf, wybiera dla każdego pakietu wersję spełniającą zadeklarowane dla niego przedziały i stara się ponownie wykorzystać tę samą kopię wszędzie tam, gdzie przedziały się pokrywają.

Gdy dwie części drzewa wymagają niekompatybilnych wersji tego samego pakietu, npm nie zgłasza błędu. Instaluje obie kopie, umieszczając drugą z nich w katalogu pakietu, który jej potrzebuje. Dlatego czasami w jednym projekcie można znaleźć dwie lub trzy wersje tej samej biblioteki.

Gdy nie da się rozwiązać konfliktu

Zagnieżdżanie nie pomaga, gdy dozwolona jest tylko jedna kopia. Tak właśnie jest w przypadku zależności równorzędnych: wtyczka deklaruje: “Działam z wersją frameworka X” i oczekuje, że będzie korzystać z instancji frameworka, którą aplikacja już posiada, zamiast dostarczać własną. Wtyczka Vue nie może dostarczać prywatnej kopii Vue; musi korzystać z tej, na której działa aplikacja.

Weźmy teraz dwie bezpośrednie zależności, z których każda z osobna jest rozsądnym wyborem. Jedna określa zakres równorzędnych wersji dla starej głównej wersji biblioteki, a druga – dla nowej. Nie ma wersji, która spełniałaby oba warunki, i nie ma możliwości zainstalowania dwóch, ponieważ chodzi właśnie o to, by korzystać z jednej. npm zatrzymuje się, zgłaszając błąd rozstrzygania zależności. Próba wymuszenia instalacji za pomocą --legacy-peer-deps Lub --force to jedynie maskuje konflikt; jeden z tych dwóch pakietów będzie teraz działał z wersją, której obsługi nigdy nie deklarował.

Rozwiązanie nigdy nie leży w drzewie. Jest to decyzja dotycząca bezpośrednich zależności: zaktualizować, zastąpić lub poczekać na opiekuna projektu. Właśnie dlatego takie konflikty wydają się niemożliwe do rozwiązania: blokuje cię wybór, którego dokonał ktoś inny w kodzie, nad którym nie masz kontroli.

Co faktycznie zawiera plik blokady

Plik blokujący (package-lock.json, yarn.lock, Lub pnpm-lock.yaml) to zapis wynikający z tej decyzji. Dla każdego pakietu w drzewie — zarówno bezpośredniego, jak i pośredniego — zawiera on informację o dokładnej wybranej wersji, źródle pobrania oraz skrócie integralności jego zawartości. W pliku lockfile npm zaznacza się również pakiety, które deklarują skrypty instalacyjne.

Ma to dwie konsekwencje:

  • Zapisz to. Bez pliku blokującego dwóch programistów instalujących oprogramowanie w odstępie jednego dnia może uzyskać różne zależności pośrednie, ponieważ zakresy są rozpoznawane jako najnowsze dostępne w danym momencie. Dzięki temu plikowi każdy użytkownik i każde uruchomienie CI otrzymuje tę samą strukturę drzewa.
  • Zainstaluj to w ramach CI. npm ci instaluje dokładnie to, co określa plik blokady, a jeśli zawartość pliku nie jest zgodna, operacja kończy się niepowodzeniem pakiet.json. npm install może to po cichu zaktualizować.

Plik blokujący to również najbardziej rzetelny spis zawartości kompilacji. Jeśli chcesz wiedzieć, co znajduje się w kompilacji, to właśnie ten plik zawiera odpowiedź, a nie pakiet.json.

Analiza drzewa

Aby zacząć, nie potrzebujesz żadnych dodatkowych narzędzi:

PytaniePolecenie
Co pobierają moje bezpośrednie zależności?npm ls --all
Dlaczego w ogóle ten pakiet się tu znalazł?npm explain
Ile mam tego egzemplarzy?npm ls
Czy można połączyć duplikaty?npm dedupe
Co uznaje się za podatne na zagrożenia?audyt npm
Eksportuj formalny spis inwentarzanpm sbom --sbom-format cyclonedx

npm explain jest tym, z czego zespoły zbyt rzadko korzystają. Daje odpowiedź na najbardziej praktyczne pytanie pojawiające się podczas każdego audytu zależności: które z naszych własnych wyborów spowodowało, że ten pakiet się tu znalazł.

Przykład z naszej praktyki

Linkster to generator mikro stron i cyfrowych wizytówek, nie wymagający programowania, przeznaczony dla przedsiębiorców i małych firm, stworzony dla klienta z Tallina w Estonii. Opracowaliśmy go od podstaw w oparciu o Vue.js.

Taki produkt frontendowy stanowi najdobrze zrozumiałą ilustrację tego zagadnienia, jaką znamy. Nie ma w nim nic niezwykłego, jeśli chodzi o zależności. Kilka bezpośrednich wyborów (framework, routing, stan, narzędzie do kompilacji, konfiguracja testowa) rutynowo przekłada się na setki pośrednich zależności, co jest oczekiwanym rezultatem, a nie sygnałem ostrzegawczym. Nikt z zespołu nie zapoznał się z kodem większości tych pakietów, co jest normalnym stanem każdego nowoczesnego frontendu.

Zatem przydatne nawyki dotyczą raczej widoczności niż ograniczania:

  • Plik lockfile stanowi część repozytorium i podlega recenzji tak samo jak każdy inny plik. Pull request, który dodaje jedną bezpośrednią zależność, ale zmienia znaczną część pliku lockfile, stanowi sygnał, że należy przyjrzeć się temu bliżej.
  • Dodanie bezpośredniej zależności to drobna decyzja, która rodzi szereg pytań: kto ją utrzymuje, kiedy ukazała się jej ostatnia wersja i co wnosi?
  • Kiedy drzewo wymaga opieki, zaczynamy od npm explain i prześledzić drogę przesyłki aż do konkretnego wyboru, który doprowadził do jej powstania, ponieważ tylko na tym poziomie możemy faktycznie coś zmienić.

Żadna z tych kwestii nie spowolniła realizacji projektu o napiętym harmonogramie. Odbywa się to w momencie dodania zależności, kiedy poświęcenie minuty na sprawdzenie nie stanowi problemu, a nie w ramach oddzielnego projektu porządkującego przeprowadzanego później.

O czym należy pamiętać: co usunąć, a co pozostawić bez zmian

Audyt zależności jest przydatny, gdy skutkuje sporządzeniem krótkiej listy konkretnych działań. Jest szkodliwy, gdy przeradza się w kampanię mającą na celu zmniejszenie liczby.

Warto to usunąć

  • Porzucone paczki. Brak nowych wersji, nierozwiązane problemy, archiwizowane lub nieistniejące repozytorium. Ryzyko nie polega na tym, że kod jest dziś wadliwy, ale na tym, że nikt go nie naprawi, gdy tak się stanie. Jeśli bezpośrednia zależność wiąże się z porzuconym kodem, poszukaj alternatywnego rozwiązania, które jest nadal rozwijane.
  • Duplikaty tej samej funkcji. Trzy biblioteki dat lub dwa klienty HTTP zazwyczaj oznaczają, że trzy osoby rozwiązały ten sam problem w różnym czasie. Wybierz jedną z nich i przeprowadź migrację.
  • Pakiety zawierające skrypty instalacyjne, których nie potrzebujesz. wstępna instalacja I po instalacji Skrypty instalacyjne uruchamiają dowolny kod na każdym komputerze, na którym instalowany jest projekt. Niektóre pakiety ich wymagają, na przykład w celu pobrania natywnego pliku binarnego. Wiele innych ich nie potrzebuje. Sprawdź, które wpisy w pliku blokady są oznaczone jako posiadające skrypty instalacyjne, i zastanów się, czy w przypadku każdego z nich istnieje ku temu uzasadnienie. W przypadku CI rozważ instalację za pomocą --ignoruj-skrypty i zezwalając tylko na te, których potrzebujesz.
  • Bezpośrednie zależności, które wykonują jedną prostą czynność. Pakiet, który otacza pojedynczy wiersz kodu z biblioteki standardowej, stanowi pełny punkt wejścia do łańcucha dostaw, nie przynosząc przy tym prawie żadnych korzyści.

Nie warto tego usuwać

  • Małe, zadbane obiekty infrastruktury w głębi lasu. Usunięcie ich zazwyczaj wiąże się z utworzeniem rozgałęzienia lub zastąpieniem bezpośredniej zależności, z której intensywnie korzystasz, a korzyść z tego wynikająca jest niezauważalna dla nikogo.
  • Wszystko, co trzeba by obejść. Zastępowanie zależności pośrednich (nadpisania (w npm) to skuteczne narzędzie służące do wprowadzania konkretnych poprawek bezpieczeństwa, a nie do porządkowania kodu.
  • Paczki wybrane wyłącznie ze względu na ich rozmiar. Rozmiar to mało miarodajny wskaźnik. Mały pakiet, za którym stoi tylko jeden anonimowy opiekun, może stanowić większe ryzyko niż duży pakiet, za którym stoi aktywny zespół.

Najlepszym kryterium oceny każdego pakietu jest to, czy ma on aktywnego autora: kogoś, kto publikuje aktualizacje, odpowiada na zgłoszenia i zauważyłby, gdyby z jego konta opublikowano coś nieoczekiwanego. To ma większe znaczenie niż liczba wierszy kodu.

W jaki sposób wiąże się to z atakami na łańcuch dostaw i SBOM

Ataki na łańcuch dostaw rzadko są wymierzone w biblioteki, które sam wybrałeś. Ich celem są te, których nie zauważyłeś: przejęte konto opiekuna małego pakietu pośredniego, złośliwy skrypt instalacyjny, nazwa łudząco podobna. Pośrednie zależności są atrakcyjne właśnie dlatego, że nikt ich nie sprawdza.

SBOM (lista komponentów oprogramowania) stanowi formalną odpowiedź na pytanie “co zawiera dana kompilacja”. Zawiera te same informacje co plik lockfile, ale w standardowym formacie, który mogą odczytać narzędzia skanujące i klienci. Jego wartość polega na tym, że jest on gotowy już w dniu pojawienia się nowego komunikatu, dzięki czemu w ciągu kilku minut można sprawdzić, czy dotyczy on danej organizacji.

Traktuj kod stron trzecich tak samo, jak traktujesz każdy system zewnętrzny. Nasz artykuł na temat modelowanie zagrożeń dla małych zespołów opisuje, jak wyznaczyć granice zaufania wokół wszystkiego, co wykonuje kod, którego nie jesteś właścicielem, a ta sama zasada ma zastosowanie do drzewa zależności. Jak argumentujemy w Bezpieczeństwo od samego początku, sprawdzanie zależności to mechanizm, który można dodać później; decyzję, którą warto podjąć już na wczesnym etapie, dotyczy mniejszej liczby, ale lepiej dobranych bezpośrednich zależności.

Wniosek

Zależności przechodnie nie stanowią problemu, ponieważ jest ich wiele. Stają się problemem dopiero wtedy, gdy są niewidoczne. Zapisz plik blokady, zainstaluj system na jego podstawie, naucz się śledzić pochodzenie każdego pakietu aż do bezpośredniego wyboru, który go wprowadził, i poświęć czas na audyt porzuconych pakietów, duplikatów oraz skryptów instalacyjnych, a nie na liczenie ich łącznej liczby. Każdą zależność oceniaj pod kątem tego, czy ktoś nadal ją utrzymuje. Jeśli chcesz mieć zespół, który tworzy produkty z zachowaniem tych nawyków od pierwszego zatwierdzenia, zapoznaj się z naszym tworzenie oprogramowania na zamówienie usługi.

Masz już ten pomysł?

Napisz do nas, a znajdziemy najlepszy sposób realizacji Twojego pomysłu!

Zamieńmy Twój pomysł w przejrzysty plan

Typ projektu

Dziękujemy!

Odpowiemy w ciągu 24 godzin.

“Nasz zespół potrafi przekształcić każdy pomysł w rozwijający się produkt”

Taras Gopko

CEO i założyciel Appricotsoft