Realne pytania, które pojawiają się przy ochronie CI/CD, są zwykle bardzo konkretne: gdzie dokładnie może dojść do wstrzyknięcia złośliwego kodu, które elementy pipeline’u są najbardziej podatne, jak odróżnić zwykłą zmianę techniczną od ruchu przygotowującego atak i co zrobić najpierw, gdy zespół nie ma czasu na pełny program bezpieczeństwa. Drugie pytanie jest jeszcze trudniejsze: co wiemy? Logi i konfiguracja pokażą część obrazu. Czego nie wiemy? Tego, czy runner, cache, obraz buildowy albo integracja zewnętrzna nie są już punktem trwałej kompromitacji.
bezpieczeństwo CI/CD, ataki na pipeline, software supply chain, sekrety w DevOps, kompromitacja runnera, złośliwe zależności, integralność artefaktów, least privilege, podpisywanie buildów, skażone obrazy kontenerowe, workflow security, incydent w pipeline
Gdzie zaczyna się problem: pipeline, który wszyscy traktują jako zaufany
Sytuacja typowa dla zespołu, który „tylko automatyzuje wdrożenia”
Najczęściej wszystko zaczyna się niewinnie. Zespół dodaje automatyczne testy, potem build, publikację obrazu, wdrożenie na środowisko testowe, później staging i produkcję. Dochodzą kolejne integracje: skanery, rejestry pakietów, system podpisywania, tajne zmienne, dostęp do chmury, webhooki, akcje zewnętrzne, pluginy do komunikatorów, narzędzia do release notes. Każdy z tych elementów osobno wygląda racjonalnie. Problem pojawia się wtedy, gdy cały ten układ nadal jest traktowany jak neutralna automatyzacja, a nie jak krytyczna część łańcucha dostaw oprogramowania.
W praktyce pipeline CI/CD bardzo szybko staje się miejscem, w którym spotykają się kod źródłowy, sekrety, uprawnienia wdrożeniowe i zaufanie organizacji. Jeżeli taki pipeline publikuje obrazy, podpisuje artefakty lub wdraża na produkcję, jego przejęcie bywa cenniejsze dla atakującego niż włamanie na pojedynczy serwer aplikacyjny. Zamiast modyfikować efekt końcowy po fakcie, można zmienić sam proces wytwarzania. To zasadnicza różnica.
Zespół często zakłada, że skoro wdrożenia działają od miesięcy, to sam mechanizm jest bezpieczny. To fałszywe poczucie kontroli. Pipeline może być stabilny operacyjnie i jednocześnie bardzo słabo broniony. Najczęstsza pułapka polega na tym, że rygor code review dotyczy kodu aplikacji, ale już nie plików workflow, skryptów build, definicji release czy kont serwisowych. Właśnie tam atakujący szuka wejścia, bo te zmiany łatwo ukryć jako „techniczne porządki”.
Dlaczego atak na CI/CD różni się od zwykłego włamania
Przy zwykłym włamaniu celem bywa konto użytkownika, pojedyncza maszyna albo aplikacja. Atak na infrastrukturę CI/CD dotyczy czegoś innego: przejęcia procesu, który produkuje zaufane oprogramowanie. Jeżeli napastnik potrafi dopisać krok do builda, podmienić zależność, wykorzystać sekrety z joba albo opublikować zmieniony artefakt do rejestru, to efekt końcowy może wyglądać całkowicie legalnie. Z punktu widzenia odbiorcy taki pakiet, obraz czy release nadal pochodzi z „oficjalnego” procesu.
To właśnie dlatego kompromitacja CI/CD bywa wyjątkowo groźna. Umożliwia jednocześnie kilka rzeczy: wstrzyknięcie złośliwego kodu, wyciek sekretów, ruch boczny do chmury albo produkcji, a nawet podpisanie skażonych artefaktów prawidłowym kluczem lub tokenem. Sam backdoor w aplikacji może być skutkiem ubocznym. Często ważniejsze jest to, że atakujący przejmuje kanał, któremu wszyscy ufają.
W praktyce trzeba rozróżniać trzy blisko powiązane obszary: kompromitację kodu źródłowego, kompromitację procesu build/deploy i kompromitację środowiska wykonawczego. Te ścieżki mogą się przenikać. Zmieniony workflow może wykraść sekret, sekret może umożliwić dostęp do rejestru pakietów, a podmieniony obraz buildowy może fałszować wynik kolejnych jobów. Gdy patrzy się wyłącznie na repozytorium, połowa obrazu znika.
Co wiemy z konfiguracji, a czego nie wiemy bez głębszej kontroli
Jeżeli zespół odpowiada za bezpieczeństwo pipeline’u, pierwsze pytanie powinno brzmieć: jakie realne możliwości ma ten proces? Nie chodzi o to, co „powinien” robić, tylko co może zrobić przy obecnych uprawnieniach. Czy może czytać sekrety produkcyjne? Czy może publikować do kilku rejestrów? Czy ma dostęp do wielu repozytoriów? Czy runner może wyjść do internetu bez ograniczeń? Czy job może pobierać i uruchamiać dowolny skrypt z zewnętrznego źródła?
Drugie pytanie brzmi: których elementów nie kontrolujemy wystarczająco? Konfiguracja workflow pokaże część prawdy, ale nie odpowie na pytanie, czy runner nie jest trwały i współdzielony, czy cache nie przechowuje sekretów, czy obraz bazowy nie został podmieniony, czy plugin nie pobiera dodatkowego kodu podczas wykonania. Bez audytu tych warstw ocena ryzyka pozostaje niepełna.
Tu właśnie pojawia się najważniejsza pułapka. Zespół zwykle koncentruje się na widocznym fragmencie procesu: YAML-ach, krokach builda, konfiguracji jobów. Tymczasem ataki na CI/CD bardzo często wykorzystują to, co uznano za „infrastrukturę pomocniczą”: obrazy, cache, integracje, uprawnienia techniczne, artefakty pośrednie. Formalnie to nadal nie jest kod aplikacji. Operacyjnie to już rdzeń zaufania.
Jak hakerzy wstrzykują złośliwy kod do procesów DevOps
Najczęstsze punkty wejścia w praktyce
Najbardziej oczywisty scenariusz to kompromitacja repozytorium i definicji workflow. Atakujący nie musi od razu zmieniać kodu aplikacji. Często wystarczy dodać pozornie techniczny krok w pipeline: pobranie zewnętrznego skryptu, nową akcję, zmianę warunku uruchamiania, dodatkowy export zmiennych środowiskowych, publikację artefaktów do innego miejsca. Taka modyfikacja może przejść jako poprawka utrzymaniowa, jeśli nikt nie stosuje równie rygorystycznego review dla plików CI jak dla logiki biznesowej.
Drugą dużą kategorią jest przejęcie sekretów używanych przez CI/CD. Chodzi o tokeny do repozytoriów, rejestrów kontenerów, systemów pakietów, kont chmurowych, narzędzi do podpisywania, serwerów wdrożeniowych i usług zewnętrznych. Wiele organizacji chroni sekrety „w teorii”, ale w praktyce one żyją w zmiennych CI, logach debug, cache zależności, obrazach, plikach konfiguracyjnych i artefaktach pośrednich. Sekret nie musi wyciec bezpośrednio. Wystarczy, że job uruchomi narzędzie wypisujące środowisko albo zapisze token do pliku, który później trafia do cache.
Kolejny częsty wektor to złośliwe lub przejęte zależności. Dotyczy to bibliotek, pakietów buildowych, narzędzi CLI, skryptów instalacyjnych, a także komponentów pobieranych dynamicznie podczas wykonywania joba. Gdy wersje nie są pinowane, a źródła nie są weryfikowane, pipeline może wprowadzić do procesu build nowe elementy bez jawnej zmiany w repozytorium aplikacji. To szczególnie niebezpieczne przy publicznych rejestrach, automatycznych aktualizacjach i zbyt szerokim zaufaniu do maintainerów zewnętrznych projektów.
Na tej samej zasadzie działają pluginy, akcje i integracje third-party. Jedna zaufana akcja z szerokimi uprawnieniami potrafi odczytać repozytorium, sekrety i artefakty, a potem wykonać połączenia sieciowe poza kontrolą głównej logiki pipeline’u. Problem nie polega wyłącznie na tym, że komponent jest „obcy”. Problem zaczyna się wtedy, gdy jego wersja nie jest przypięta do konkretnego identyfikatora, źródło nie jest weryfikowane, a zespół traktuje go jak gotowy klocek niewymagający przeglądu bezpieczeństwa.
Kompromitacja runnera i środowiska buildowego
Runner lub agent buildowy bardzo często jest niedoszacowany pod względem ryzyka. Formalnie to tylko maszyna wykonująca zadania. Technicznie to host, który widzi kod, sekrety, artefakty i ścieżki sieciowe do kolejnych systemów. Jeżeli taki runner jest trwały, współdzielony między projektami, ma szeroki outbound do sieci i przechowuje lokalne cache, staje się idealnym miejscem do cichej kompromitacji.
Atakujący nie musi od razu przejmować całego systemu CI. Czasem wystarczy pozostawić ślad na runnerze: zmodyfikowane narzędzie w ścieżce systemowej, zainfekowany cache, podmieniony skrypt pomocniczy, niewyczyszczony katalog roboczy, zmienione zmienne środowiskowe lub agent nasłuchujący danych z kolejnych jobów. W środowiskach współdzielonych taki ruch może rozciągnąć incydent na wiele repozytoriów i zespołów.
Podobnie działa skażony obraz kontenerowy lub obraz VM używany do buildów. Jeżeli zespół ufa obrazowi bazowemu bez jego weryfikacji, to uruchamia cały proces build na fundamencie, którego integralności nie sprawdził. Taki obraz może zawierać zmodyfikowane narzędzia kompilacji, złośliwe hooki, dodatkowe certyfikaty, nietypowe repozytoria pakietów albo mechanizm eksfiltracji sekretów podczas wykonywania builda. Z punktu widzenia developera wszystko wygląda normalnie: testy przechodzą, artefakt się buduje, release wychodzi. Problem ukrywa się warstwę niżej.
Manipulacja artefaktami, cache i kontami serwisowymi
Wiele zespołów myśli o bezpieczeństwie na etapie wejścia do pipeline’u, ale pomija środkową i końcową część procesu. Tymczasem artefakty pośrednie, cache i rejestry pakietów są częstym celem. Jeżeli ktoś może podmienić artefakt między buildem a deployem, zmodyfikować warstwę obrazu w rejestrze, zatruć cache zależności albo opublikować pakiet o tej samej nazwie do zaufanego źródła, to złośliwy kod trafia dalej bez zmiany w repozytorium źródłowym.
To szczególnie ważne tam, gdzie istnieją procesy wieloetapowe: osobny build, osobne testy bezpieczeństwa, osobna publikacja i osobny deploy. Jeżeli brakuje silnego powiązania między artefaktem a konkretnym buildem, zespół nie ma pewności, że wdraża dokładnie to, co zostało przetestowane. Bez podpisywania i weryfikacji integralności łatwo zgubić odpowiedź na podstawowe pytanie: czy to na pewno ten sam pakiet?
Ostatni duży obszar to nadużycia kont serwisowych. Konta techniczne bywają uprzywilejowane bardziej niż ludzie, a jednocześnie rzadziej monitorowane. Często mają długowieczne tokeny, dostęp do wielu repozytoriów, możliwość publikacji do rejestru i uruchamiania wdrożeń, a niekiedy również prawa administracyjne w chmurze. Gdy takie konto zostanie przejęte, napastnik może ominąć część zabezpieczeń opartych na tożsamości człowieka, MFA czy ręcznych akceptacjach.
| Element pipeline’u | Typowy sposób nadużycia | Skutek |
|---|---|---|
| Repozytorium i workflow | Zmiana skryptu build/deploy lub triggera | Wstrzyknięcie kodu, wyciek sekretów |
| Sekrety CI | Eksfiltracja tokenów z joba, logów lub cache | Dostęp do chmury, rejestrów, produkcji |
| Zależności i pakiety | Podmiana lub przejęcie komponentu zewnętrznego | Skażony build, backdoor w artefakcie |
| Runner | Trwała kompromitacja hosta wykonawczego | Rozszerzenie ataku na wiele jobów |
| Obraz buildowy | Podmienione narzędzia lub złośliwe repozytoria | Niewidoczna manipulacja wynikiem builda |
| Artefakty i rejestry | Podmiana po buildzie lub zatrucie cache | Deploy niezweryfikowanego pakietu |
Dwie krótkie sytuacje z praktyki, które wyglądają niewinnie
„Techniczna poprawka” w workflow, która otwiera drogę do wycieku
To jeden z najbardziej realistycznych scenariuszy. Ktoś zgłasza drobną zmianę w pipeline: uporządkowanie logów, debug po nieudanym buildzie, nowy krok diagnostyczny, uproszczenie eksportu zmiennych albo poprawkę działania na branchach tymczasowych. Na pierwszy rzut oka nic groźnego. W praktyce taka zmiana może dodać polecenie wypisujące część środowiska, zapisujące token do pliku, wysyłające artefakt do zewnętrznego endpointu albo uruchamiające skrypt pobierany z internetu.
Co jest tu istotne? Nie sama wielkość zmiany, lecz jej znaczenie bezpieczeństwa. Dodanie jednego kroku shellowego może być groźniejsze niż duży refaktor aplikacji. Jeżeli review koncentruje się tylko na tym, czy build nadal działa, a nie na tym, czy job uzyskał nowe możliwości sieciowe lub dostęp do sekretów, atak ma szansę przejść bez alarmu.
Przy przeglądzie takich zmian trzeba patrzeć na konkretne sygnały:
- czy pojawiły się nowe odwołania do sekretów, które wcześniej nie były potrzebne,
- czy job uruchamia inline shell tam, gdzie wcześniej używał jawnej, wersjonowanej logiki,
- czy dodano połączenia wychodzące do nowych domen lub usług,
- czy zmieniło się źródło obrazu buildowego,
- czy zmienił się sposób publikacji artefaktów, logów albo cache,
- czy zakres uprawnień workflow lub tokena został rozszerzony „tymczasowo”, bez jasnego powodu.
Z pozoru to kosmetyka. W praktyce często właśnie tam ukrywa się najprostsza ścieżka ataku. Pytanie kontrolne brzmi: co dokładnie nowy krok może odczytać, uruchomić i wysłać na zewnątrz? Drugie jest równie ważne: czego zespół już nie zobaczy, jeśli ten krok wykona się tylko przy błędzie, tylko na wybranym branchu albo tylko w pipeline uruchamianym ręcznie?
Niewinna aktualizacja zależności, po której wszystko dalej działa
Drugi scenariusz jest jeszcze trudniejszy do wychwycenia, bo nie daje oczywistych objawów. Ktoś podnosi wersję paczki, zmienia akcję CI na nowszą, aktualizuje obraz buildowy albo dopina „przydatne” narzędzie instalowane w locie. Build przechodzi, testy przechodzą, release wygląda normalnie. Co wiemy? Że proces się nie zatrzymał. Czego nie wiemy? Czy nowy komponent nie dodał pobocznych połączeń sieciowych, nie zmienił sposobu pobierania zależności albo nie uzyskał szerszego dostępu do środowiska.
To częsty błąd oceny ryzyka: skoro aplikacja działa, zmiana wydaje się bezpieczna. A przecież wiele ataków na CI/CD nie psuje działania produktu. Ich celem jest ciche przejęcie procesu po drodze. Dlatego przy „zwykłych” aktualizacjach trzeba sprawdzać nie tylko numer wersji, ale też źródło, zakres uprawnień, sposób dystrybucji i to, czy komponent był przypięty do konkretnego identyfikatora. Jeden brak takiego sprawdzenia potrafi zamienić techniczną rutynę w trwały punkt wejścia.
Bezpieczny pipeline nie jest bezpieczny dlatego, że ma dużo kroków kontrolnych. Jest bezpieczny wtedy, gdy zespół wie, którym elementom ufa, dlaczego im ufa i jak szybko zauważy, że to zaufanie właśnie zostało wykorzystane przeciwko niemu.
Najczęstsze pułapki, przez które zespoły same otwierają drogę do kompromitacji
W praktyce problem rzadko zaczyna się od „zaawansowanego ataku”. Częściej od skrótu operacyjnego, który miał pomóc dowieźć release szybciej. Sam skrót nie musi być błędem. Błędem staje się wtedy, gdy daje nową ścieżkę do kodu, sekretów albo środowiska wdrożeniowego, a nikt tego nie traktuje jak zmiany bezpieczeństwa.
Zbyt szerokie uprawnienia, bo „tak jest wygodniej”
To jedna z najbardziej powtarzalnych pułapek. Workflow dostaje uprawnienia do odczytu i zapisu tam, gdzie realnie potrzebuje tylko odczytu. Runner może publikować artefakty, choć miał tylko uruchamiać testy. Konto serwisowe widzi kilka rejestrów, kilka środowisk i część zasobów chmurowych, bo kiedyś „było łatwiej skonfigurować jedno konto”.
Fakt jest prosty: atakujący korzysta z tych uprawnień, które już istnieją. Nie musi ich od razu eskalować. Jeśli job testowy może publikować pakiety albo uruchamiać deploy, to sam pipeline staje się drogą do produkcji. Co wiemy? Że wszystko działa wygodnie. Czego nie wiemy? Czy pojedynczy kompromitowany krok nie ma dziś większej mocy niż człowiek po MFA.
Najczęściej ryzykowne są trzy sytuacje:
- jeden token używany do wielu funkcji: build, publikacja, deploy, administracja,
- workflow uruchamiany przez pull request ma dostęp do sekretów produkcyjnych lub półprodukcyjnych,
- konto techniczne może zmieniać konfigurację pipeline’u i jednocześnie publikować jego wynik.
Brak separacji między środowiskami i typami jobów
Zespół często rozdziela środowiska na poziomie aplikacji, ale nie rozdziela ich wystarczająco w samym CI/CD. Testy, build release i deploy działają na tej samej klasie runnerów, z podobnym dostępem sieciowym i tym samym zestawem narzędzi. Z perspektywy bezpieczeństwa to ryzyko, bo słabszy etap procesu dziedziczy zaufanie silniejszego.
Przykład z życia codziennego zespołów: job uruchamiany dla gałęzi roboczej działa na tym samym runnerze co build wydaniowy. Formalnie to dwa różne przebiegi. Operacyjnie dzielą host, cache, czasem obraz i ścieżki dostępu. Jeżeli mniej zaufany job zostawi ślad na wykonawcy, kolejny może już pracować na skażonym gruncie.
Oddzielenie powinno dotyczyć nie tylko środowisk typu dev, stage i prod. Równie ważne jest rozdzielenie:
- jobów od niezweryfikowanego kodu i jobów od release,
- runnerów współdzielonych i runnerów do zadań uprzywilejowanych,
- sekretów potrzebnych do testu i sekretów potrzebnych do publikacji lub deployu.
Brak kontroli nad zmianami w definicjach pipeline’u
W wielu organizacjach plik workflow albo skrypt deployowy przechodzi lżejsze review niż kod aplikacji. To niebezpieczne odwrócenie priorytetów. Definicja pipeline’u nie jest „tylko konfiguracją”. To wykonywalna logika z dostępem do sekretów, sieci i artefaktów.
Jeżeli zmiany w pipeline definitions mogą wejść bez obowiązkowego przeglądu, bez ochrony branchy i bez wymogu dodatkowej akceptacji dla zadań uprzywilejowanych, to zespoły same tworzą kanał do cichego wstrzyknięcia kodu. Dotyczy to również skryptów pomocniczych trzymanych poza głównym repozytorium albo pobieranych w locie z zewnętrznego źródła.
Dobra praktyka nie polega na tym, by każdą zmianę blokować ręcznie. Chodzi o to, by zmiany wpływające na wykonanie jobów, sekrety, tokeny, obrazy, pluginy i połączenia sieciowe miały wyraźnie wyższy próg zaufania niż zwykła korekta dokumentacji.
Długowieczne sekrety i brak krótkiego cyklu rotacji
Token, który działa miesiącami i daje szeroki dostęp, jest wygodny do utrzymania, ale równie wygodny dla napastnika. Jeżeli wycieknie z logów, cache, zrzutu pamięci, błędnie skonfigurowanego debugowania albo z kompromitowanego runnera, może być używany długo po tym, jak sam incydent przestanie być widoczny.
To jeden z tych obszarów, gdzie „mamy sekret manager” nie oznacza jeszcze bezpieczeństwa. Znaczenie mają szczegóły: jak długo żyje poświadczenie, kto może je pobrać, czy jest przypisane do konkretnego przebiegu joba, czy można ograniczyć jego użycie do jednego celu i jednej tożsamości. Sekret przechowywany poprawnie, ale wystawiany zbyt szeroko i zbyt długo, nadal jest problemem.
Pinowanie tylko części zależności
Zespoły coraz częściej pilnują wersji bibliotek aplikacyjnych, ale pomijają inne elementy łańcucha: akcje CI, pluginy, obrazy bazowe, narzędzia instalowane przez skrypt, a nawet repozytoria pakietów skonfigurowane dynamicznie podczas builda. W rezultacie proces wygląda na kontrolowany tylko fragmentarycznie.
Jeżeli przypięta jest wersja biblioteki, ale nie jest przypięty identyfikator obrazu, hash akcji albo źródło instalacji narzędzia, to zaufanie nadal opiera się częściowo na bieżącym stanie zewnętrznego ekosystemu. Tego nie da się uczciwie nazwać stabilnym łańcuchem dostaw.
Sygnały alarmowe: kiedy trzeba sprawdzić pipeline i artefakty
Nie każdy nietypowy log oznacza incydent. Z drugiej strony część realnych naruszeń nie daje spektakularnych objawów. Dlatego liczą się nie pojedyncze anomalie, tylko ich kontekst. Jeśli kilka drobnych sygnałów pojawia się naraz, pipeline trzeba potraktować jak potencjalnie skażony, a nie tylko „kapryśny”.
Zmiany, które wyglądają technicznie, ale zmieniają zaufanie
Do pilnej weryfikacji kwalifikują się sytuacje, w których:
- workflow zaczyna używać nowych sekretów albo nowych zakresów tokenów,
- job nagle potrzebuje dostępu do internetu tam, gdzie wcześniej działał lokalnie,
- zmienia się źródło obrazu buildowego, pluginu, akcji lub repozytorium pakietów,
- pojawia się nowy etap publikacji artefaktu, który nie jest spięty z dotychczasową kontrolą integralności,
- logika uruchamiania rozszerza się na ręczne triggery, branch tymczasowy lub pull request z mniej zaufanego źródła.
Każdy z tych przypadków może mieć uzasadnienie operacyjne. Problem zaczyna się wtedy, gdy zmiana przechodzi bez odpowiedzi na dwa pytania: jaki nowy dostęp właśnie został dodany i czy ten dostęp jest konieczny dokładnie w tym miejscu pipeline’u.
Anomalie w logach, cache i ruchu sieciowym
Na poziomie wykonania alarmujące są zwłaszcza: nieoczekiwane połączenia wychodzące, pobieranie narzędzi z nowych lokalizacji, zapis nietypowych plików w katalogach roboczych, zmiana rozmiaru lub składu artefaktów bez logicznej przyczyny, a także cache, który „nagle zaczął pomagać” różnym projektom w zaskakująco podobny sposób.
Bywa też prościej. Build, który od dawna działał przewidywalnie, zaczyna sporadycznie drukować dodatkowe informacje środowiskowe, uruchamiać retry w nietypowych miejscach albo tworzyć pliki diagnostyczne, których nikt nie planował. To nie dowód ataku, ale wystarczający powód, żeby porównać przebieg z wcześniejszymi, zaufanymi wykonaniami.
Różnice między tym, co zbudowano, a tym, co wdrożono
Jeśli nie da się jednoznacznie powiązać wdrożonego artefaktu z konkretnym buildem i konkretnym commitem, organizacja traci podstawowy punkt odniesienia. Wtedy nawet poprawne testy bezpieczeństwa nie dają pełnej odpowiedzi, bo nie ma pewności, czego dokładnie dotyczyły.

To ważny sygnał szczególnie wtedy, gdy:
- ten sam tag obrazu pojawia się w różnych momentach z inną zawartością,
- artefakt w rejestrze nie zgadza się z tym, co wskazują logi builda,
- podpis istnieje, ale nie jest weryfikowany przed wdrożeniem,
- proces promocji między środowiskami odtwarza artefakt zamiast przenosić ten sam, wcześniej sprawdzony pakiet.
Decyzje obronne, które dają efekt bez przebudowy wszystkiego
Nie każdy zespół może od razu przeprojektować całe CI/CD. I nie zawsze trzeba. W wielu środowiskach największą poprawę daje kilka zmian, które porządkują granice zaufania zamiast dokładać kolejne skanery bez wpływu na realny tor ataku.
Najpierw ogranicz zasięg pojedynczego joba
Jeżeli trzeba wybrać jeden priorytet, zwykle wygrywa redukcja uprawnień i sekretów widocznych dla pojedynczego przebiegu. Job testowy nie powinien móc wdrażać. Job od pull requesta nie powinien widzieć sekretów release. Runner od mniej zaufanego kodu nie powinien współdzielić stanu z zadaniami uprzywilejowanymi.
To nie brzmi widowiskowo, ale właśnie tam ogranicza się skutki kompromitacji. Nawet jeśli ktoś wstrzyknie złośliwy krok, jego zasięg kończy się szybciej.
Uszczelnij to, co zespół traktuje jako „pomocnicze”
Często największym problemem nie jest główna definicja workflow, lecz to, co ją otacza: skrypty shellowe, obrazy bazowe, pluginy, własne akcje, cache, narzędzia instalowane w locie. Te elementy bywają pomijane w przeglądzie, bo nie są „aplikacją”. A przecież to one realnie wykonują pracę podczas builda.
Dobrym ruchem jest wprowadzenie prostego rozróżnienia:
- co jest wersjonowane i przypięte do konkretnego identyfikatora,
- co jest pobierane dynamicznie,
- co ma wpływ na artefakt końcowy lub sekrety,
- co może być zmienione bez dodatkowej akceptacji, a co nie.
Już samo takie uporządkowanie zwykle ujawnia miejsca, w których zaufanie było budowane bardziej na przyzwyczajeniu niż na kontroli.
Mały zespół i większa organizacja nie mają tych samych priorytetów
W mniejszym zespole najwięcej daje prostota: mniej runnerów współdzielonych, mniej długowiecznych sekretów, mniej dynamicznie pobieranych komponentów i twarda kontrola zmian w pipeline definitions. Tam często da się szybko poprawić bezpieczeństwo przez ograniczenie wyjątków.
W bardziej złożonej organizacji sam porządek konfiguracyjny nie wystarczy. Pojawia się potrzeba separacji klas runnerów, lepszego śledzenia pochodzenia artefaktów, silniejszej kontroli kont serwisowych i jasnych reguł promocji między środowiskami. Inaczej różne zespoły będą miały lokalnie „działające” rozwiązania, które globalnie tworzą niespójny łańcuch zaufania.
Po wykryciu incydentu: co odciąć, co sprawdzić, co odtworzyć
Jeśli istnieje podejrzenie kompromitacji pipeline’u, pierwszym błędem bywa skupienie się wyłącznie na repozytorium źródłowym. To za mało. Trzeba potraktować incydent szerzej: jako możliwe naruszenie kodu, wykonawców, sekretów, artefaktów i kont technicznych jednocześnie.
Pierwszy ruch: zatrzymanie zaufania, nie tylko zatrzymanie builda
Samo wyłączenie pipeline’u nie rozwiązuje problemu, jeśli złośliwe artefakty są już w rejestrze, runner pozostał skażony, a token dalej działa. Dlatego pierwsze decyzje zwykle obejmują:
- wstrzymanie publikacji i wdrożeń z podejrzanych ścieżek,
- rotację sekretów używanych przez zagrożone joby, runnery i konta serwisowe,
- izolację lub całkowitą wymianę runnerów, które mogły wykonać skażony kod,
- oznaczenie artefaktów z podejrzanego okresu jako nieufnych do czasu weryfikacji.
Tu liczy się dyscyplina. Jeśli organizacja nie potrafi wskazać, które artefakty powstały na których runnerach i z użyciem jakich poświadczeń, zakres incydentu rośnie niemal automatycznie.
Potem weryfikacja łańcucha: od commita do wdrożonego pakietu
Najważniejsze jest odtworzenie ciągu zależności. Który commit uruchomił build? Jaki workflow i jakie skrypty zostały wykonane? Z jakiego obrazu startował job? Jakie sekrety były dostępne? Jaki artefakt został opublikowany, podpisany i wdrożony? Bez tego zespół widzi tylko fragmenty zdarzenia.
W praktyce trzeba sprawdzić równolegle kilka warstw:
- historię zmian w workflow, skryptach i konfiguracji integracji,
- logi runnerów oraz ich trwały stan, jeśli taki istniał,
- użycie kont serwisowych i tokenów w czasie incydentu,
- zgodność artefaktów, tagów, podpisów i metadanych builda,
- czy z podejrzanych jobów nie doszło do ruchu dalej: do chmury, rejestru, systemów deployowych lub produkcji.
Odtwarzanie ze stanu pewnego, nie „ostatniego działającego”
Po incydencie kuszące bywa przywrócenie „ostatniego działającego” obrazu albo rerun ostatniego zielonego pipeline’u. To ryzykowne, bo ostatni działający przebieg mógł już korzystać z przejętego komponentu albo runnera. Bezpieczniej odbudować zaufane środowisko wykonawcze, odtworzyć sekrety, przypiąć źródła zależności i dopiero wtedy wykonać nowy build ze zweryfikowanego punktu w repozytorium.
To wolniejsze niż szybki rollback, ale daje odpowiedź na dwa kluczowe pytania: co wiemy na pewno i czego jeszcze nie wiemy. Jeśli nie ma pewności co do integralności builda, bezpieczniej założyć szerszy zakres skażenia niż zbyt wcześnie przywrócić automatyzację. W praktyce oznacza to też czasowe wyłączenie „wygodnych” skrótów: ręcznych wyjątków, omijania weryfikacji podpisów czy ponownego użycia starego cache.
Dopiero po takim oczyszczeniu ma sens analiza przyczyny źródłowej. Nie tylko „jaki commit był zły”, ale też dlaczego przeszedł: czy zawiódł przegląd zmian, czy runner miał zbyt szeroki dostęp, czy system nie rozróżniał kodu zaufanego od mniej zaufanego, czy może artefakt dało się podmienić już po buildzie. To rozróżnienie ma znaczenie, bo bez niego organizacje często naprawiają objaw i zostawiają ten sam tor ataku na przyszłość.
Zwykle właśnie wtedy wychodzą na jaw rzeczy pozornie poboczne. Token techniczny bez daty wygaśnięcia. Akcja przypięta do gałęzi zamiast do konkretnego identyfikatora. Runner współdzielony przez zadania o różnych poziomach zaufania. Każdy z tych elementów osobno nie musi oznaczać incydentu, ale po zdarzeniu układają się w bardzo czytelny wzór.
CI/CD nie psuje się zwykle jednym spektakularnym błędem. Częściej kompromitacja przechodzi przez serię drobnych zgód na wyjątki, uproszczenia i zaufanie „bo dotąd działało”. I właśnie tam najczęściej zaczyna się obrona: nie od większej liczby narzędzi, lecz od twardszych granic, lepszej identyfikowalności i mniejszej liczby miejsc, w których złośliwy kod może zostać potraktowany jak zwykła część procesu.
Które zabezpieczenia tylko wyglądają dobrze
W wielu zespołach problem nie polega na całkowitym braku ochrony, tylko na fałszywym poczuciu bezpieczeństwa. Coś jest „włączone”, więc uznaje się temat za zamknięty. Tyle że przy CI/CD liczy się nie obecność mechanizmu, lecz to, czy rzeczywiście przecina realną ścieżkę ataku.
Skaner zależności bez kontroli źródła i wersji
Samo skanowanie podatności nie rozwiązuje problemu przejętej lub podmienionej zależności. Narzędzie może nie wykazać niczego podejrzanego, jeśli pakiet nie ma jeszcze znanego CVE, a mimo to zawiera złośliwy kod albo został opublikowany pod mylącą nazwą.
Tu liczą się trzy pytania: skąd pakiet został pobrany, czy wersja była przypięta i czy zespół rozróżnia pakiety dopuszczone od pobieranych „w locie”. Bez tego skaner bywa tylko filtrem na jedną klasę problemów.
Podpisy artefaktów, których nikt nie sprawdza przy wdrożeniu
Sam fakt podpisania obrazu lub paczki nie daje jeszcze ochrony. Jeśli proces deployu nie odrzuca artefaktów bez ważnego podpisu albo nie sprawdza, kto ten podpis wystawił, mechanizm istnieje głównie na papierze.
To częsta pułapka organizacyjna. Zespół buildowy mówi: „podpisujemy”, zespół wdrożeniowy zakłada, że to wystarczy, a po drodze nikt nie wymusza weryfikacji. W efekcie da się wdrożyć coś, co wygląda jak produkt zaufanego pipeline’u, ale nim nie jest.
MFA na kontach ludzi, lecz nie na ścieżkach automatycznych
Uwierzytelnianie wieloskładnikowe dla administratorów repozytorium czy platformy CI jest potrzebne, ale nie zamyka problemu nadużyć przez tokeny, konta serwisowe i integracje. Atakujący często nie musi logować się jako człowiek. Wystarczy, że przejmie sekret używany przez automatyzację.
Jeżeli token może publikować pakiety, modyfikować workflow albo uruchamiać deployment bez dodatkowych ograniczeń, to właśnie on staje się kluczowym celem. Nie ekran logowania, tylko cichy kanał techniczny.
„Ephemeral runner”, który w praktyce nie jest efemeryczny
Runner określany jako tymczasowy bywa uruchamiany na obrazie, który przechowuje cache, współdzielony katalog roboczy albo poświadczenia odziedziczone z warstwy hosta. Formalnie job startuje od nowa. Operacyjnie część stanu zostaje.
To dobry moment na prostą kontrolę: co zostaje po zakończeniu zadania, a co jest niszczone bez wyjątku? Co wiemy na pewno? Czego nie wiemy, bo nikt tego nie sprawdzał poza założeniem „przecież runner jest tymczasowy”?
Miejsca, które wymagają osobnej kontroli zamiast ogólnego „zadbajmy o bezpieczeństwo”
Nie cały łańcuch dostaw trzeba analizować z tą samą intensywnością. Są punkty, które zasługują na osobne reguły, bo ich kompromitacja daje szybki dostęp do produkcji, sekretów albo możliwości podpisania złośliwego artefaktu jako legalnego.
Definicje workflow i skrypty pomocnicze
Zmiany w kodzie aplikacji zwykle przechodzą przez przegląd. Zmiany w plikach pipeline’u i skryptach pomocniczych już nie zawsze. A to właśnie tam wystarczy dopisać jeden krok pobierający dodatkowy komponent, wyświetlający sekrety lub modyfikujący artefakt po testach.
W praktyce sens ma osobna polityka dla takich plików: bardziej restrykcyjny review, ograniczenie tego, kto może zatwierdzać zmiany, i wyraźne oznaczenie, że modyfikacja workflow to nie „konfiguracja”, tylko zmiana o wysokim wpływie bezpieczeństwa.
Źródła zależności i rejestry pakietów
Jeżeli organizacja korzysta z wielu publicznych i prywatnych źródeł, łatwo zgubić kontrolę nad tym, skąd naprawdę pochodzi komponent użyty w buildzie. Tu nie chodzi tylko o malware. Problemem jest też niejednoznaczność: dwa pakiety o podobnej nazwie, kilka rejestrów, fallback do publicznego źródła, gdy prywatne chwilowo nie odpowiada.
To jeden z tych obszarów, gdzie mały błąd konfiguracyjny może dać dużą ekspozycję. Dlatego rejestry, proxy pakietów i reguły rozwiązywania nazw lepiej traktować jako element krytyczny, a nie wyłącznie operacyjne ułatwienie.
Konta serwisowe z dostępem dalej niż do samego builda
Jeśli konto używane w CI potrafi jednocześnie pobrać kod, opublikować obraz, zmienić konfigurację w chmurze i uruchomić wdrożenie, to pojedyncze przejęcie otwiera kilka etapów łańcucha naraz. Wtedy granice między kompromitacją pipeline’u a kompromitacją środowiska wykonawczego praktycznie znikają.
Najwięcej zysku daje tu rozdzielenie ról. Inny kontekst dla testów, inny dla publikacji, inny dla wdrożenia. To mniej wygodne niż jeden uniwersalny token, ale znacząco utrudnia przejście od złośliwego commita do pełnego ruchu bocznego.
Dwa krótkie scenariusze, które często są lekceważone
Pull request uruchamia „bezpieczne” testy, ale dostaje za dużo
Zespół chce szybko testować zmiany z zewnątrz, więc workflow dla pull requestów ma dostęp do części sekretów „bo inaczej integracja nie ruszy”. Z pozoru kompromis jest niewielki. W praktyce wystarczy sprytnie przygotowany kod lub modyfikacja skryptu testowego, aby wydobyć token i użyć go poza pipeline’em.
To nie musi od razu oznaczać pełnego przejęcia. Czasem skutkiem jest tylko możliwość publikacji pakietu do wewnętrznego rejestru. Ale właśnie takie ograniczone nadużycia często zostają niezauważone najdłużej.
Własna akcja lub plugin „wewnętrzny”, więc uznany za zaufany
Organizacje często tworzą własne komponenty automatyzacji i po pewnym czasie przestają je traktować jak kod wysokiego ryzyka. Są wewnętrzne, znane, używane od dawna. Problem zaczyna się wtedy, gdy taki plugin pobiera coś dynamicznie, ma szerokie uprawnienia albo jego repozytorium jest słabiej chronione niż główny kod produktu.
Atakujący nie musi iść najtrudniejszą drogą. Jeśli da się zmienić pomocniczy komponent używany przez wiele pipeline’ów, efekt bywa szerszy niż po kompromitacji jednego repozytorium aplikacyjnego.
Jak ustawić priorytety, gdy nie ma czasu na pełny audyt
Nie każde środowisko da się szybko przeanalizować warstwa po warstwie. Gdy trzeba podjąć decyzje pod presją czasu, sens ma kolejność oparta na wpływie na zaufanie, a nie na tym, co najłatwiej „odhaczyć”.
Najpierw dobrze sprawdzić punkty, które jednocześnie wykonują kod i mają dostęp do sekretów lub publikacji artefaktów. Potem miejsca, gdzie można zmienić definicję procesu bez wyraźnej kontroli. Dopiero później dodatki, które poprawiają widoczność, ale same nie blokują ataku.
- czy kod mniej zaufany uruchamia się w tym samym kontekście co release,
- czy istnieją tokeny o długim czasie życia i szerokim zakresie,
- czy obrazy, akcje, pluginy i zależności są przypinane do konkretnych identyfikatorów,
- czy artefakt da się powiązać z dokładnym buildem, runnerem i zestawem użytych komponentów,
- czy zmiana workflow ma taką samą wagę kontrolną jak zmiana kodu produkcyjnego.
Jeżeli na któreś z tych pytań odpowiedź brzmi „nie wiemy”, to zwykle jest to lepszy kandydat do pilnej pracy niż kolejny dashboard bezpieczeństwa.
Gdzie kończy się teoria, a zaczyna audyt konkretnego środowiska
Typowe ścieżki ataku są dość dobrze rozpoznane: przejęte sekrety, nadużycie tokenów, złośliwe zależności, modyfikacja workflow, skażone runnery. Tego nie trzeba zgadywać. Nie da się jednak uczciwie ocenić skali ryzyka bez odpowiedzi na pytania bardzo przyziemne: kto może zmieniać pipeline, jakie są relacje między runnerami, skąd naprawdę pobierane są komponenty i czy organizacja umie odtworzyć pochodzenie wdrożonego artefaktu.
Właśnie tam wychodzi różnica między ogólną zgodnością a realną odpornością. Dwa zespoły mogą używać tej samej platformy CI i tych samych narzędzi skanujących, a mimo to mieć zupełnie inny poziom ekspozycji. Jeden izoluje zadania, rotuje poświadczenia i blokuje niezweryfikowane artefakty. Drugi polega na domyślnych ustawieniach i kilku wyjątkach, które z czasem stały się normą.
Ataki na CI/CD rzadko zaczynają się od czegoś egzotycznego. Znacznie częściej wykorzystują miejsce, które było „tymczasowe”, „pomocnicze” albo „wewnętrzne”, więc nikt nie traktował go jak granicy bezpieczeństwa. I to zwykle jest najtrzeźwiejszy punkt wyjścia do dalszych decyzji.
Najczęściej zadawane pytania (FAQ)
Jak hakerzy najczęściej wstrzykują złośliwy kod do pipeline’u CI/CD?
Najczęściej nie zaczyna się od spektakularnego włamania, tylko od małej zmiany, która wygląda technicznie i nie budzi podejrzeń. Atakujący może zmodyfikować plik workflow, dodać krok pobierający zewnętrzny skrypt, podmienić zależność, użyć przejętej akcji third-party albo wykorzystać token CI do publikacji zmienionego artefaktu.
W praktyce najczęstsze ścieżki są dość powtarzalne:
- zmiana definicji workflow lub skryptu build,
- kradzież sekretów z jobów, logów lub cache,
- podmiana zależności, obrazów buildowych albo pluginów,
- kompromitacja runnera wykonującego zadania,
- nadużycie zaufanej integracji z rejestrem, chmurą lub systemem podpisywania.
Najgroźniejsze jest to, że końcowy artefakt nadal może wyglądać legalnie. Został przecież zbudowany i opublikowany przez „oficjalny” proces.
Które elementy CI/CD są najbardziej podatne na atak?
Najwięcej uwagi zwykle idzie w stronę kodu aplikacji, a to nie on bywa najsłabszym ogniwem. Często bardziej ryzykowne są pliki workflow, skrypty build, akcje zewnętrzne, runner, cache, obrazy kontenerowe używane do budowania oraz sekrety podpięte do jobów.
Dobrze zadać dwa krótkie pytania: co wiemy, patrząc na konfigurację? Czego nie wiemy bez głębszej kontroli? YAML pokaże tylko część obrazu. Nie powie sam z siebie, czy runner jest współdzielony, czy cache nie przechowuje tokenów, albo czy obraz bazowy nie został wcześniej skażony.
Po czym poznać, że zmiana w pipeline’ie może przygotowywać atak?
Sygnałem ostrzegawczym nie musi być jawnie złośliwy kod. Częściej chodzi o zmiany, które dają dodatkowe możliwości: nowy dostęp do sekretów, pobieranie skryptu z internetu podczas builda, publikację artefaktów do nowego miejsca, użycie akcji bez przypiętej wersji albo rozszerzenie uprawnień konta serwisowego „bo inaczej job nie działa”.
W praktyce podejrzane są też poprawki opisane jako porządki techniczne, które omijają standardowy review. Krótki przykład: ktoś dodaje krok debugujący środowisko i przez przypadek lub celowo wypisuje zmienne zawierające tokeny. Taka zmiana może wyglądać niewinnie, ale skutki są już operacyjne, nie kosmetyczne.
Jakie sekrety w DevOps najczęściej wyciekają i gdzie się to dzieje?
Najczęściej wyciekają tokeny do repozytoriów, rejestrów kontenerów, menedżerów pakietów, kont chmurowych, systemów wdrożeniowych i narzędzi do podpisywania buildów. Problem rzadko ogranicza się do jednego miejsca. Sekret może znaleźć się w zmiennej CI, logu debug, pliku tymczasowym, artefakcie pośrednim, cache zależności albo w obrazie używanym do builda.
To jeden z częstszych błędów zespołów: sekrety są „schowane” w platformie CI, więc uznaje się temat za zamknięty. Tymczasem o bezpieczeństwie decyduje to, kto może je odczytać podczas joba, gdzie trafiają po wykonaniu i czy runner nie zachowuje śladów między uruchomieniami.
Czy runner CI/CD może być głównym punktem kompromitacji?
Tak, i to częściej, niż zakładają zespoły. Runner widzi kod źródłowy, sekrety, artefakty i zwykle ma połączenia sieciowe do kolejnych systemów. Jeśli jest trwały, współdzielony albo słabo izolowany, atakujący może zostawić na nim mechanizm, który przetrwa między jobami i będzie wpływał na kolejne buildy.
Tu problem jest prosty: co wiemy? Że job przeszedł. Czego nie wiemy? Czy środowisko wykonawcze było czyste, czy nie odziedziczyło zmienionego cache, narzędzia albo podmienionego obrazu. Dlatego samo przejrzenie logów pipeline’u często nie wystarcza do zamknięcia incydentu.
Jak odróżnić zwykły problem techniczny od incydentu bezpieczeństwa w pipeline?
Awaria techniczna zwykle kończy się błędem builda albo wdrożenia. Incydent bezpieczeństwa częściej daje subtelniejsze objawy: nowy ruch sieciowy z joba, niespodziewane odczyty sekretów, publikację artefaktów poza standardowym rejestrem, zmianę uprawnień konta serwisowego albo build, który działa „normalnie”, ale używa innego źródła zależności niż wcześniej.
Niepokój powinny wzbudzić też rozjazdy między repozytorium a tym, co faktycznie wykonał runner. Na przykład definicja workflow się nie zmieniła, ale job pobrał dodatkowy skrypt z zewnątrz albo użył nowszej wersji akcji bez świadomej decyzji zespołu. W takich przypadkach traktowanie sprawy jako zwykłego „flaku CI” bywa kosztownym uproszczeniem.
Co zrobić najpierw, jeśli zespół nie ma czasu na pełny program bezpieczeństwa CI/CD?
Najpierw trzeba ograniczyć zaufanie tam, gdzie dziś jest automatyczne. Sensowny pierwszy ruch to przegląd uprawnień: które joby naprawdę potrzebują dostępu do sekretów, produkcji, rejestrów i chmury. Drugie działanie to uszczelnienie źródeł wykonania, czyli przypięcie wersji akcji, obrazów i zależności oraz usunięcie kroków pobierających dowolny kod w trakcie builda.
Na start zwykle największy efekt dają:
- least privilege dla jobów i kont serwisowych,
- osobne review dla plików CI/CD i skryptów build,
- krótkotrwałe sekrety zamiast stałych tokenów,
- izolowane, jednorazowe runnery tam, gdzie to możliwe,
- podpisywanie buildów i kontrola integralności artefaktów.
To nie zamyka całego ryzyka, ale odcina część najprostszych scenariuszy ataku. W środowisku DevOps właśnie te „tymczasowe wyjątki” najczęściej stają się trwałą luką.






