1. Zacznij od pełnej mapy zależności, nie od wyszukiwarki CVE
Bez inwentarza nie ma wiarygodnego audytu
Projekt może mieć w manifeście zaledwie kilka bibliotek, a mimo to faktycznie korzystać z setek komponentów open source. Dzieje się tak przez zależności przechodnie, czyli pakiety pobierane przez pakiety, których zespół nawet nie wybierał świadomie. To właśnie w nich często pojawiają się CVE w bibliotekach, problemy licencyjne albo komponenty dawno niewspierane przez autorów. [4]
Audyt bezpieczeństwa zależności powinien zaczynać się od odpowiedzi na proste pytanie: co dokładnie trafia do aplikacji, obrazu kontenera, paczki instalacyjnej albo firmware’u? Bez tego wynik skanera będzie niepełny, a decyzje o aktualizacjach mogą opierać się na błędnych założeniach.
W praktyce trzeba rozróżnić kilka rodzajów zależności:
- bezpośrednie – deklarowane ręcznie przez zespół w pliku manifestu,
- przechodnie – instalowane jako wymagania innych pakietów,
- opcjonalne – uruchamiane tylko w określonych konfiguracjach lub na konkretnych systemach,
- deweloperskie – używane do testów, budowania, lintowania albo generowania dokumentacji,
- runtime – dostarczane do środowiska produkcyjnego i dostępne podczas działania usługi.
To rozróżnienie ma znaczenie. Podatność w narzędziu do generowania dokumentacji zwykle wymaga innej reakcji niż podatność w parserze używanym przez publiczne API. Nie oznacza to jednak, że zależności deweloperskie można całkowicie ignorować. Zainfekowane narzędzie budujące może zaatakować pipeline CI/CD, przejąć token do rejestru pakietów albo zmodyfikować artefakt jeszcze przed wdrożeniem.
Manifest i lockfile mówią o projekcie różne rzeczy
Plik manifestu pokazuje intencję autora projektu, natomiast plik blokady wersji pokazuje, co zostało faktycznie rozstrzygnięte przez menedżer pakietów. Przy sprawdzaniu zależności open source oba źródła są potrzebne, ale nie należy traktować ich zamiennie.
| Ekosystem | Manifest | Plik blokady lub źródło dokładnych wersji |
|---|---|---|
| Node.js | package.json | package-lock.json, pnpm-lock.yaml, yarn.lock |
| Python | requirements.txt, pyproject.toml | poetry.lock, uv.lock, zamrożony plik requirements |
| Java i JVM | pom.xml, build.gradle | drzewo zależności wygenerowane przez Maven lub Gradle |
| Go | go.mod | go.sum |
| Rust | Cargo.toml | Cargo.lock |
Przykładowo wpis "biblioteka": "^2.4.0" w package.json nie mówi jeszcze, że produkcja działa na wersji 2.4.0. Lockfile może wskazywać wersję 2.8.1, a inny pakiet może wymuszać równolegle jeszcze starszą gałąź tej samej biblioteki. Skanowanie samego manifestu może więc przeoczyć komponent, który rzeczywiście znajduje się w artefakcie.
Mit: „znamy używane biblioteki, bo widzimy je w manifeście”. Rzeczywistość jest mniej wygodna: krytyczna podatność często trafia do aplikacji przez pakiet przechodni, którego nikt nie dodał ręcznie i którego nazwy zespół może nawet nie rozpoznawać.
SBOM jako trwała lista komponentów w wydaniu
Software Bill of Materials, czyli SBOM, to uporządkowany spis komponentów użytych do zbudowania konkretnego artefaktu. Dobrze przygotowany SBOM zawiera co najmniej nazwę pakietu, wersję, ekosystem, źródło pochodzenia i relacje między komponentami. Może także obejmować identyfikatory licencji, sumy kontrolne oraz metadane producenta.
Najczęściej spotykane formaty to CycloneDX i SPDX. CycloneDX jest mocno ukierunkowany na bezpieczeństwo i analizę zależności, a SPDX jest szeroko używany również w kontekście licencyjnym i zgodności łańcucha dostaw. Na etapie startowym ważniejsza od wyboru „idealnego” formatu jest powtarzalność: SBOM powinien powstawać automatycznie dla każdego wydania, a nie być ręcznie tworzonym dokumentem raz w roku.
Dobry proces wygląda tak: pipeline buduje aplikację, tworzy obraz kontenera lub paczkę, generuje SBOM na podstawie finalnego artefaktu, a następnie archiwizuje go razem z numerem wersji i sumą kontrolną. Gdy po kilku miesiącach zostanie ujawnione nowe CVE, można ustalić nie tylko, czy biblioteka była używana w repozytorium, lecz także które konkretne wersje wdrożone do produkcji zawierały zagrożony komponent.
2. Sprawdzaj CVE w kilku źródłach i rozumiej, co naprawdę oznacza wynik
CVE, CVSS, CPE i GHSA — pojęcia, które zmieniają interpretację alertu
CVE to identyfikator publicznie opisanej podatności, na przykład CVE-2024-xxxxx. Sam numer nie mówi jeszcze, czy problem dotyczy konkretnej aplikacji ani jak pilny jest dla danego zespołu. Jest punktem odniesienia, który pozwala połączyć opis błędu, wersje podatnego oprogramowania, poprawkę i informacje o exploitach.
CVSS jest skalą oceny technicznej dotkliwości podatności. Wynik wysoki lub krytyczny sygnalizuje poważny potencjał szkody, ale nie zastępuje analizy kontekstu. Biblioteka z oceną CVSS 9.8 może być pilniejsza lub mniej pilna zależnie od tego, czy podatna funkcja jest używana, czy usługa jest dostępna z Internetu i czy atakujący ma realny sposób dostarczenia danych wejściowych.

CPE to sposób opisywania produktów i wersji, wykorzystywany między innymi w bazie NVD. W świecie open source dopasowania oparte wyłącznie na CPE bywają niedokładne, ponieważ nazwy pakietów, forków i modułów nie zawsze odpowiadają nazwom produktów w tej klasyfikacji. Dlatego narzędzia oparte na ekosystemowych identyfikatorach pakietów często lepiej radzą sobie z npm, PyPI, Maven Central, Cargo czy Go Modules.
GHSA, czyli GitHub Security Advisory, jest identyfikatorem doradztwa bezpieczeństwa publikowanego w bazie GitHub Advisory Database. Jedna podatność może mieć jednocześnie identyfikator GHSA i CVE. Warto zapisywać oba, bo opis w jednym źródle bywa bardziej praktyczny, a w drugim szybciej pojawia się informacja o poprawionej wersji. [5]
Źródła podatności dla ekosystemów open source
Jedno źródło alertów nie zapewnia pełnego obrazu. Bazy aktualizują się w różnym tempie, mają odmienne reguły łączenia pakietów z podatnościami i nie zawsze identycznie opisują zakres zagrożonych wersji. Brak wyniku w jednym skanerze nie jest dowodem, że zależność jest bezpieczna. [2]
Praktyczny zestaw źródeł do weryfikacji obejmuje:
- OSV i osv.dev – przydatne dla wielu ekosystemów open source, szczególnie tam, gdzie ważne są dokładne zakresy wersji,
- GitHub Advisory Database – użyteczna przy projektach hostowanych na GitHubie oraz w połączeniu z Dependabot alerts,
- NVD – szeroka baza CVE, przydatna do śledzenia szczegółów, choć wymagająca ostrożnej interpretacji dopasowań,
- advisories rejestrów pakietów – informacje publikowane przez ekosystemy npm, PyPI, Maven, RubyGems i inne,
- kanały bezpieczeństwa maintainerów – release notes, pliki
SECURITY.md, listy mailingowe, issue trackery i oficjalne komunikaty projektów.
Dla konkretnego stosu technologicznego przydają się również narzędzia natywne dla ekosystemu. W projektach JavaScript można uruchomić npm audit albo pnpm audit. Dla Pythona pomocny jest pip-audit, dla Rusta cargo audit, a dla projektów JVM narzędzia analizujące zależności Mavena i Gradle’a. OWASP Dependency-Check, Snyk, Dependabot i Renovate mogą uzupełniać ten zestaw na poziomie repozytorium oraz pipeline’u.
Mit: „brak alertów w jednym skanerze oznacza brak CVE”. Rzeczywistość: skaner może nie znać danego pakietu, nie obsługiwać jego ekosystemu, opóźniać aktualizację bazy albo błędnie rozpoznać wersję. Z tego powodu krytyczne komponenty dobrze jest sprawdzać przynajmniej w dwóch niezależnych źródłach.
Jak czytać raport bez automatycznego podnoszenia paniki
Raport skanowania zależności open source należy traktować jako listę spraw do weryfikacji, nie jako gotową listę awarii. Pierwszym krokiem jest potwierdzenie identyfikacji pakietu: nazwy, wersji, źródła oraz tego, czy komponent rzeczywiście znajduje się w artefakcie. Kolejny krok to sprawdzenie zakresu podatnych wersji i pierwszej wersji zawierającej poprawkę.

Trzeba też odróżnić sytuację „brak poprawki” od sytuacji „narzędzie nie znalazło poprawki”. Czasami poprawiona wersja istnieje, ale baza nie ma jeszcze prawidłowego mapowania. Innym razem maintainers opisują obejście w komunikacie bezpieczeństwa, choć nie opublikowali nowego wydania. Ręczna lektura oryginalnego advisory jest szczególnie ważna przy alertach krytycznych oraz przy zależnościach wykorzystywanych w przetwarzaniu danych użytkownika.
Raport powinien zostać wzbogacony o informacje użyteczne dla zespołu: identyfikator CVE lub GHSA, komponent i wersję, ścieżkę zależności, środowisko użycia, link do źródła, dostępność poprawki, osobę odpowiedzialną oraz termin decyzji. Sam eksport CSV z narzędzia nie tworzy jeszcze procesu bezpieczeństwa.
3. Odróżniaj krytyczny alert od ryzyka, które nie jest osiągalne
Triaging: trzy pytania przed pilną aktualizacją
Każdy alert o CVE powinien przejść krótki triage. Celem nie jest szukanie wymówek, by nie aktualizować bibliotek, lecz ustalenie właściwej kolejności działań. W praktyce trzy pytania często wystarczają, aby odróżnić realny incydent od problemu o ograniczonym znaczeniu.
- Czy komponent znajduje się w środowisku produkcyjnym? Biblioteka może być obecna wyłącznie w testach, narzędziach lokalnych albo obrazie budującym. Nadal może wymagać poprawy, lecz niekoniecznie musi zatrzymać wdrożenie aplikacji.
- Czy podatna funkcja jest osiągalna? Należy sprawdzić, czy aplikacja wywołuje zagrożony kod, ładuje podatny moduł, przetwarza wymagany format danych albo pozwala użytkownikowi uruchomić określoną ścieżkę.
- Czy atakujący kontroluje wymagane wejście i ma odpowiedni dostęp? Inaczej ocenia się błąd wymagający lokalnego konta administratora, a inaczej błąd możliwy do wykorzystania przez anonimowego użytkownika publicznej usługi.
Ocena osiągalności wymaga dowodów, nie intuicji
Osiągalność nie zawsze da się ustalić przez samo wyszukanie nazwy funkcji w kodzie. Podatny fragment może być wywoływany pośrednio przez framework, plugin, mechanizm automatycznej deserializacji albo konfigurację serwera. Z drugiej strony obecność pakietu w obrazie nie oznacza automatycznie, że zagrożona ścieżka jest aktywna.
Przydatne dowody podczas triage’u to między innymi:
- wyniki wyszukiwania wywołań podatnego API w kodzie i konfiguracji,
- test odtwarzający warunki opisane w advisory,
- logi potwierdzające, czy dana ścieżka jest wykonywana w produkcji,
- reguły routingu, polityki sieciowe i ustawienia uwierzytelniania,
- informacja, czy podatna funkcja jest domyślnie wyłączona lub wymaga specjalnej konfiguracji.
Przykład: advisory dotyczy parsera archiwów ZIP i możliwości zapisu plików poza katalogiem roboczym. Aplikacja może mieć ten pakiet jako zależność przechodnią, ale używać go wyłącznie do odczytu własnych, podpisanych archiwów w wewnętrznym procesie wsadowym. Ryzyko jest inne niż w publicznym endpointcie przyjmującym pliki ZIP od anonimowych użytkowników. W obu przypadkach aktualizacja pozostaje właściwym celem, lecz drugi scenariusz wymaga znacznie szybszej reakcji.
Nie należy jednak mylić „nie znaleźliśmy wywołania” z „podatność jest nieosiągalna”. Wniosek o braku osiągalności powinien mieć właściciela, uzasadnienie i datę ponownej oceny. Zmiana konfiguracji, nowy endpoint albo aktualizacja frameworka może później uruchomić ścieżkę, która wcześniej była nieaktywna.
Ustal priorytet według ekspozycji i możliwości naprawy
CVSS pomaga zrozumieć techniczną wagę problemu, ale kolejkę prac warto budować według ryzyka dla konkretnej usługi. Prosty model priorytetyzacji może uwzględniać pięć czynników:
- Ekspozycję: czy usługa jest dostępna z Internetu, sieci partnerskiej czy tylko z segmentu administracyjnego.
- Osiągalność: czy podatny kod może zostać uruchomiony przez rzeczywisty przepływ danych.
- Wpływ biznesowy: jakie dane, uprawnienia i procesy są zagrożone.
- Dojrzałość ataku: czy istnieje publiczny exploit, aktywna kampania lub prosty sposób wykorzystania błędu.
- Łatwość usunięcia problemu: czy dostępna jest bezpieczna aktualizacja, zmiana konfiguracji albo skuteczne obejście.
Praktyczna skala może wyglądać następująco:
- P0: podatność osiągalna w publicznej usłudze, z wysokim wpływem lub aktywnym exploitem; reakcja natychmiastowa.
- P1: komponent produkcyjny, realna ścieżka ataku, ale ograniczona ekspozycja albo wymagane uwierzytelnienie; naprawa w krótkim, zdefiniowanym terminie.
- P2: problem w produkcji bez potwierdzonej osiągalności; aktualizacja planowana wraz z uzasadnieniem i terminem weryfikacji.
- P3: zależność deweloperska, testowa lub nieobecna w finalnym wdrożeniu; problem rejestrowany i obsługiwany w normalnym cyklu utrzymaniowym.
Najważniejsza jest konsekwencja. Zespół powinien potrafić odpowiedzieć, dlaczego alert krytyczny otrzymał niższy priorytet oraz kiedy ta decyzja zostanie ponownie sprawdzona. Bez tego „akceptacja ryzyka” szybko staje się trwałą listą niezałatwionych wyjątków.
4. Rozpoznawaj porzucone pakiety po sygnałach, a nie po jednej dacie commita
Brak nowego commita od roku nie przesądza, że pakiet jest porzucony. Stabilna biblioteka o wąskim zakresie może nie wymagać częstych zmian. Z kolei projekt z codziennymi commitami może być ryzykowny, jeśli poprawki bezpieczeństwa nie trafiają do wydań albo nikt nie odpowiada na zgłoszenia.
Szukaj zestawu sygnałów ostrzegawczych
O kondycji zależności lepiej świadczy kilka obserwacji niż pojedyncza metryka. Warto ocenić zwłaszcza:
- czas reakcji na błędy i zgłoszenia bezpieczeństwa — otwarte krytyczne problemy bez odpowiedzi są ważniejszym sygnałem niż brak zwykłych feature requestów,
- częstotliwość publikowania wydań — czy poprawki trafiają do rejestru, a nie pozostają tylko w gałęzi głównej,
- jasność procesu bezpieczeństwa — obecność
SECURITY.md, kanału kontaktowego i informacji o wspieranych wersjach, - liczbę aktywnych maintainerów — projekt zależny od jednej nieaktywnej osoby ma większe ryzyko ciągłości, [3]
- stan zależności własnych pakietu — nieaktualne komponenty przechodnie mogą wskazywać na zaniedbane utrzymanie,
- zgodność z aktualnym ekosystemem — brak kompatybilności z obecnymi wersjami runtime’u, kompilatora lub frameworka bywa praktycznym sygnałem końca życia projektu,
- komunikaty o migracji — czasem autorzy wprost wskazują następcę, fork albo nowy moduł.
Przykład: pakiet nie miał release’u od 18 miesięcy, ale maintainer odpowiada na zgłoszenia, publikuje poprawki w gałęzi stabilnej i dokumentuje wspierane wersje. Nie jest to automatycznie pakiet porzucony. Inaczej wygląda biblioteka z wieloma otwartymi problemami bezpieczeństwa, nieaktualnym runtime’em i informacją, że właściciel nie utrzymuje już projektu.

Oceń koszt zastąpienia zanim pojawi się kryzys
Dla ważnych pakietów warto prowadzić prosty rejestr „zależności wymagających obserwacji”. Nie musi to być rozbudowana baza. Wystarczą informacje o właścicielu technicznym, zastosowaniu w systemie, alternatywach i szacowanym koszcie migracji.
Gdy pakiet zaczyna wykazywać sygnały porzucenia, zespół ma zwykle cztery opcje:
- pozostać przy nim tymczasowo, ograniczając ekspozycję i monitorując nowe problemy,
- przejść na wspierany odpowiednik lub rekomendowanego następcę,
- użyć utrzymywanego forka, po sprawdzeniu jego reputacji i procesu publikacji,
- przejąć utrzymanie wewnętrznie, jeśli komponent jest mały, krytyczny i migracja byłaby zbyt kosztowna.
Własny fork nie powinien być decyzją „na później”. Oznacza odpowiedzialność za aktualizacje, publikowanie poprawek, testy zgodności i obserwowanie nowych podatności. Jest uzasadniony tylko wtedy, gdy organizacja realnie ma zasoby, aby go utrzymywać.
5. Wybierz narzędzia, które działają w CI/CD, a nie tylko na laptopie
Lokalne skanowanie pomaga programiście przed wysłaniem zmian, ale nie daje gwarancji powtarzalności. W CI/CD można sprawdzać dokładnie ten sam kod, lockfile i artefakt, który przechodzi do wdrożenia, a wyniki przypisać do konkretnego commita oraz wydania.
Podziel automatyzację na szybkie i głębsze kontrole
Nie każdy etap pipeline’u powinien wykonywać pełny, długi skan. Praktyczny układ rozdziela kontrole według celu:
- Pull request: szybkie skanowanie zmian w zależnościach, wykrywanie nowych alertów i kontrola niezatwierdzonych wyjątków.
- Build głównej gałęzi: pełniejsza analiza projektu, generowanie raportów oraz kontrola polityk jakości i bezpieczeństwa.
- Budowa artefaktu: skan obrazu kontenera, paczki lub binarium wraz z komponentami systemowymi.
- Przed wdrożeniem: egzekwowanie progów ryzyka dla środowiska docelowego, z możliwością udokumentowanego wyjątku.
- Skanowanie okresowe: ponowna analiza już wydanych artefaktów, ponieważ nowe advisories mogą pojawić się bez zmiany kodu.
Przykładowo pipeline może blokować merge, gdy zmiana dodaje nową osiągalną podatność o priorytecie P0 lub P1. Nie powinien natomiast bezrefleksyjnie blokować całego projektu przez historyczny alert P3 w narzędziu testowym, jeśli istnieje zaakceptowany plan aktualizacji. Polityka ma chronić produkt, a nie generować obchody procesu. [1]
Dbaj o jakość danych wejściowych i wyników
Narzędzie w CI/CD będzie użyteczne tylko wtedy, gdy ma aktualne dane oraz poprawnie rozpoznaje środowisko. W praktyce oznacza to kilka zasad:
- uruchamiaj skan na odtworzonych, deterministycznych zależnościach,
- uwzględniaj osobno zależności aplikacyjne, systemowe i znajdujące się w obrazach kontenerowych,
- przechowuj raporty jako artefakty pipeline’u, aby można było porównać wynik między wydaniami,
- aktualizuj bazy podatności narzędzi skanujących,
- integruj wyniki z systemem zgłoszeń tylko dla alertów wymagających działania, aby nie tworzyć tysięcy bezużytecznych ticketów,
- kontroluj wyjątki jako kod lub wersjonowaną konfigurację, z datą wygaśnięcia i osobą zatwierdzającą.
Warto mierzyć nie tylko liczbę alertów. Lepsze wskaźniki to czas od wykrycia do decyzji, czas do aktualizacji, liczba wygasłych wyjątków oraz udział artefaktów objętych regularnym skanowaniem. Taki zestaw pokazuje, czy proces faktycznie skraca okno ryzyka.
6. Reaguj na wykryte CVE i porzucone zależności według ustalonej ścieżki
Największym problemem zwykle nie jest samo wykrycie alertu, lecz chaos po jego otrzymaniu. Ustalona ścieżka reakcji pozwala uniknąć zarówno ignorowania ważnych problemów, jak i kosztownych aktualizacji wykonywanych bez testów.
Stosuj powtarzalny proces obsługi alertu
- Zarejestruj problem. Zapisz identyfikator advisory, komponent, wersję, usługę, środowisko i osobę odpowiedzialną za analizę.
- Przypisz priorytet. Oprzyj go na ekspozycji, osiągalności, wpływie i dostępności exploita, a nie wyłącznie na wartości CVSS.
- Wybierz sposób ograniczenia ryzyka. Preferuj aktualizację do poprawionej wersji. Jeśli nie jest możliwa, rozważ konfigurację, ograniczenie wejścia, wyłączenie funkcji albo izolację usługi.
- Przetestuj zmianę. Aktualizacja zależności może zmieniać API, zachowanie parsera, domyślne ustawienia TLS lub format danych. Uruchom testy automatyczne oraz testy krytycznych przepływów biznesowych.
- Wdróż i potwierdź. Po wdrożeniu sprawdź, czy nowa wersja znalazła się w artefakcie oraz czy kolejny skan nie wykrywa nadal tego samego komponentu.
- Zamknij albo odnów wyjątek. Jeśli ryzyko zostało tymczasowo zaakceptowane, wyjątek musi mieć uzasadnienie, właściciela, kompensujące zabezpieczenia i konkretną datę wygaśnięcia.
Inaczej obsługuj CVE, a inaczej problem porzuconego projektu
CVE ma zwykle konkretny zakres wersji i możliwą poprawkę. Porzucona zależność jest natomiast ryzykiem strategicznym: dziś może nie mieć znanej podatności, ale jutro może nie otrzymać aktualizacji kompatybilnej z nowym runtime’em lub krytycznej poprawki bezpieczeństwa.
Dla pakietu uznanego za porzucony warto utworzyć zadanie migracyjne niezależnie od bieżących alertów. Dobre zadanie zawiera:
- wybraną alternatywę albo decyzję o forku,
- zakres kodu i usług objętych zmianą,
Kluczowe Wnioski
- Audyt zacznij od pełnej mapy zależności, obejmującej komponenty bezpośrednie, przechodnie, opcjonalne, deweloperskie i runtime.
- Porównuj manifest z lockfile oraz skanuj finalny artefakt, ponieważ to on trafia do środowiska produkcyjnego.
- Generuj SBOM dla każdego wydania, aby móc szybko ustalić, które wersje zawierały podatny komponent.
- Weryfikuj CVE w kilku źródłach, takich jak OSV, GitHub Advisory Database, NVD i advisories rejestrów pakietów.
- Nie oceniaj alertu wyłącznie na podstawie CVSS. Sprawdź, czy komponent jest używany, osiągalny i dostępny z zewnątrz.
Pytania od czytelników
Czy skanowanie obrazu kontenera zastępuje analizę repozytorium?
Co zrobić, gdy narzędzie nie wskazuje wersji zawierającej poprawkę?
Jak ustalić, czy podatna zależność naprawdę trafia do produkcji?
Czy zależności deweloperskie można pominąć w audycie?
Źródła
- [1] Google Open Source Security Team – OSV-Scanner — narzędzie do wykrywania znanych podatności w zależnościach ↑
- [2] Google Open Source Security Team – OSV-Scanner — użycie i polecenia skanowania ↑
- [3] OpenSSF – OpenSSF Scorecard — dokumentacja kontroli bezpieczeństwa ↑
- [4] OpenSSF – OpenSSF Scorecard — definicja kontroli Maintained ↑
- [5] GitHub Docs – Dependabot alerts — dokumentacja GitHub ↑
