Wiosną 2026 dwie firmy padły ofiarą tej samej klasy ataku: skradzione poświadczenia, cudze klucze API i masowa konsumpcja Gemini na ich koszt. Pierwszy incydent skończył się rachunkiem na pół miliona euro. Drugi, dwa miesiące później, trafił na środowisko objęte już naszym monitoringiem i został zatrzymany w kilka godzin. Ten tekst opowiada o tym, co zbudowaliśmy pomiędzy tymi dwiema datami, i dlaczego najtrudniejsze nie było samo wykrywanie anomalii, tylko decyzja, kiedy naprawdę alarmować.
Pół miliona euro w trzy dni
Maj 2026. Duża grupa technologiczna, prowadząca m.in. platformy e-commerce i streamingowe, używa Google Cloud symbolicznie. Główna infrastruktura znajduje się gdzie indziej; na GCP działają jedynie systemy poboczne, generujące około 500 dolarów miesięcznie. Stabilnie, od lat, bez historii.
W połowie miesiąca firma współorganizuje duże, szeroko promowane wydarzenie technologiczne. Zespół inżynierski w całości jest na miejscu i ma ręce pełne roboty. To właśnie w te dni ktoś sięga po wyciekły klucz starego konta serwisowego, następnie w jednym z projektów podnosi sobie uprawnienia do roli Editor, włącza Vertex AI i uruchamia inferencję na modelu Gemini 1.5 Pro. Nie testowo. Przemysłowo.
Efekt? Rachunek za zużycie zasobów GCP w okresie 14–16 maja sięga 500 960 euro. Tysiąc miesięcznych budżetów tej firmy nabitych w trzy doby.
Najbardziej gorzka część tej historii jest taka, że alerty formalnie zadziałały. Google wysłał powiadomienie o podejrzeniu nadużycia i przekroczeniu średnich wydatków już 14 maja. Tyle że pierwszy alert budżetowy, przy średniej 500 dolarów, opiewał na kwotę, która w wartościach bezwzględnych wyglądała niegroźnie. A ludzie, którzy mieli go przeczytać, stali akurat na scenie. Pełna reakcja ruszyła 17 maja i była wzorowa: konta wyłączone, złośliwe powiązania IAM usunięte, projekt zamrożony w trybie read-only do analizy śledczej, nowe polityki bezpieczeństwa w całej organizacji. Śledztwo potwierdziło, że dane nie wyciekły, a szkody ograniczyły się do jednego projektu. Ale licznik zdążył się już zatrzymać na pół miliona.
Zwykły dzień na tym koncie to kilkanaście euro, więc na tej skali takie słupki są po prostu niewidoczne. To nie jest zrzut ekranu z prawdziwej konsoli.
Dane z wykresu (tabela)
| Dzień | typowy | 14 maja | 15 maja | 16 maja |
|---|---|---|---|---|
| Koszt | ≈ €16 | ≈ €292 000 | ≈ €168 000 | ≈ €41 000 |
Z tego incydentu wyszły trzy wnioski, które trafiły prosto do naszego backlogu. Statyczne alerty budżetowe są ślepe w środowiskach z małym spendem, bo procentowo gigantyczny wzrost wygląda na starcie jak drobne. Powiadomienia o nadużyciach idą wyłącznie do właściciela środowiska; partner rozliczający standardowo nie widzi nic, dopóki klient sam się nie odezwie. No i nie istniał żaden automatyczny bezpiecznik, który by tę konsumpcję przydusił.
Postanowiliśmy, że u naszych klientów takie incydenty pierwsi będziemy zauważać my, a nie faktura. Tak powstał Cloud Cost Sentinel.
Koszt jest sygnałem bezpieczeństwa
Branża ma już na to nazwę: LLMjacking. Skradzione poświadczenia chmurowe nie służą do wykradania danych, tylko do masowej konsumpcji drogich API modeli językowych, często odsprzedawanej dalej. Rachunek zostaje u ofiary. Publicznie opisywane przypadki to kwoty od kilkunastu do kilkudziesięciu tysięcy dolarów u pojedynczych deweloperów i małych zespołów; opisywane tu firmy nie były wyjątkiem, tylko kolejnymi pozycjami w dobrze naoliwionym procederze.
Dlatego traktujemy telemetrię billingową tak, jak SecOps traktuje logi: jako strumień zdarzeń do ciągłej analizy. Nagły wzrost kosztu prawie zawsze ma techniczną przyczynę, a spora część tych przyczyn to incydenty:
Architektura: od eksportu billingu do PagerDuty
Obsługujemy setki kont billingowych Google Cloud. Każde z nich ma pełny eksport billingu do partycjonowanej tabeli BigQuery. To zresztą jedyna rzecz, jakiej system wymaga od środowiska. Na tym fundamencie działa detektor: lekki, bezstanowy serwis w Cloud Run, wybudzany harmonogramem w krótkich, regularnych cyklach.
Dane
Detekcja
Decyzja
Reakcja
Wszystkie elementy są bezstanowe. Jedynym stanem systemu jest historia detekcji zapisywana z powrotem do BigQuery; to ona napędza dashboard i decyzje o ponownym alertowaniu. Alerty przychodzą też do nas, nie tylko do klienta.
Cztery pytania, które zadajemy danym w każdym cyklu analizy
Każdy typ anomalii to inne pytanie i inne okno odniesienia. Wspólny mianownik jest tutaj taki, że każde konto porównujemy wyłącznie z jego własną historią. Konkretne progi, długości okien i podłogi kwotowe to parametry konfiguracji dobierane per wdrożenie, dlatego nie rozpisujemy ich tutaj co do liczby.
| Detektor | Pytanie | Punkt odniesienia |
|---|---|---|
| Dzienny (z-score) | Czy wczorajszy albo dzisiejszy koszt odstaje od normy? | Kroczące okno ostatnich dni, bez dnia bieżącego; alarm przy statystycznie istotnym odchyleniu powyżej kwotowej podłogi |
| Godzinowy spike | Czy ta godzina jest gwałtownie droższa niż zwykle? | Profil godzinowy konta z ostatnich dni; alarm przy wielokrotności typowego kosztu tej godziny |
| Nowe konto | Czy świeżo utworzone konto od razu dużo wydaje? | Konto bez historii, które natychmiast generuje wysoki spend; jeden z najsilniejszych sygnałów nadużycia |
| Prognoza miesięczna | Dokąd zmierza ten miesiąc? | Ekstrapolacja month-to-date na tle poprzednich miesięcy; alarm, gdy prognoza wyraźnie ucieka |
Serce detektora dziennego to jedno zapytanie okienne w BigQuery i akurat ta część wdrożenia jest najprostsza. Prawdziwa złożoność kryje się w szczegółach: dzień bieżący celowo zostaje poza baseline’em (Dlaczego? Okaże się za chwilę…), okna i progi trzeba dostroić do realnych danych, a samym danym nie wolno ślepo ufać, ani ich kompletności, ani układowi. Brzmi jak drobiazgi, ale każdy z nich kiedyś zabolał.
Lipiec: ta sama klasa ataku, inny finał
Dwa miesiące po majowym incydencie ten sam scenariusz ataku uderzył w naszego klienta, niewielkie studio aplikacji mobilnych. Środowisko to było już wtedy objęte monitoringiem Cloud Cost Sentinel. Początek był banalny: jeden z deweloperów chciał zainstalować popularne narzędzie i kliknął sponsorowany wynik wyszukiwania. Strona wyglądała jak trzeba, instalator też. W środku siedział stealer z kampanii, którą publicznie dokumentowały m.in. Bitdefender i Trend Micro. Mimo że konto miało włączone MFA, niczego to nie dało, bo ten typ malware’u działa z wnętrza już zalogowanego komputera. Kradnie sesję, która uwierzytelnienie ma już dawno za sobą.
Wieczorem, 4 lipca, skradzionymi poświadczeniami zalogował się skrypt z serwera hostingowego w Niemczech. Po latach współpracy przy Firebase konto miało rolę Editor w kilkunastu projektach, co w Google Cloud oznacza między innymi prawo do włączania API i tworzenia kluczy. Automat potrzebował 3 minut i 16 sekund, żeby przejść po projektach i zostawić w nich 16 włączonych API, 9 kont serwisowych i 28 kluczy. Potem na dwanaście dni zapadła cisza. Klucze leżały i czekały.
16 lipca o 13:45 klucze ożyły i masowa konsumpcja Gemini ruszyła we wszystkich projektach naraz. O 4:30 nad ranem, gdy tylko pierwsze dane tej doby dotarły do eksportu billingowego, detektor podniósł alarm. Karta CRITICAL wyglądała następująco: z-score grubo ponad pięćset, wzrost liczony w dziesiątkach tysięcy procent, a w rozbiciu na usługi praktycznie cały przyrost pochodził z jednej pozycji: Gemini API, usługi wcześniej na tym koncie w ogóle nieużywanej. Poszła eskalacja.
Ten klient nie korzystał z naszego stałego wsparcia i nie nadawał nam uprawnień do swojego środowiska, więc nasza rola kończyła się na alarmie i eskalacji. Zablokować nadużycie mógł tylko on sam. Zespół klienta podjął temat na początku dnia: około 08:20 ruch wrócił do baseline’u, a do 12:40 obce klucze i konta serwisowe zniknęły ze wszystkich projektów.
Baseline tego konta to około 20 dolarów dziennie. Jeden dzień aktywnego nadużycia wystarczył, żeby wielokrotnie przebić łączne wydatki konta z całego roku. To nie jest zrzut ekranu z konsoli klienta.
Dane z wykresu (tabela)
| Dzień | typowy | 16 lipca | 17 lipca |
|---|---|---|---|
| Koszt | ≈ $20 | ≈ $33 250 | ≈ $650 |
Gdy eksport dopełnił dobę, licznik zatrzymał się na trzydziestu trzech tysiącach dolarów, a odchylenie urosło do 842 sigm. Dzień zamknął się 906 razy powyżej normy konta. Na osi czasu wygląda to tak: konsumpcja ruszyła po 13:45, alarm wyszedł o 4:30, bo tyle zajęło dotarcie danych do eksportu billingowego, a cała reszta to czas reakcji po stronie klienta. Był piątek. Majowa historia pokazuje dość dokładnie, co by się stało, gdyby ta konsumpcja spokojnie przepracowała weekend. Tam, gdzie mamy nie tylko mandat do eskalacji, ale też uprawnienia do reakcji, te kilka godzin potrafi skurczyć się do kilku minut.
Alert, który sam siebie gasił
Drugiego dnia lipcowego incydentu konto wciąż paliło 14,2 tys. dolarów na dobę. A nasz z-score dla tego dnia wyniósł… 1,1. Statystycznie? Nic ciekawego, proszę się rozejść.
Powód: okno kroczące zdążyło wchłonąć dzień szczytu do własnego baseline’u. Średnia krocząca skoczyła z 20 dolarów do tysięcy, odchylenie standardowe eksplodowało i trwający incydent zaczął wyglądać na nową normę. Alert krytyczny degradował się do ostrzeżenia, choć wydatki nie spadły ani o cent.
Pierwsza poprawka była statystyczna: wykluczać dni stanowiące anomalie z baseline’u, licząc go w dwóch przebiegach. Nie zadziałała. Drugi dzień incydentu miał już zaniżony z-score, więc nie łapał się na flagę outliera i od trzeciego dnia znowu zatruwał okno. Dopiero po revercie zrozumieliśmy, że walczymy nie z tym przeciwnikiem: to nie jest problem statystyki, tylko cyklu życia alertu. Alert to stan, nie pojedyncze zdarzenie.
Epizod się zapala. Karta w Chat, zadanie w CRM, przy krytycznych także PagerDuty. Zapis do historii detekcji.
Severity jest podłogą. Zatruty z-score nie może zdegradować alertu ani odpalić duplikatu. Cisza, chyba że incydent realnie eskaluje.
Epizod gaśnie. Po kilku dniach bez nowego alertu kafelek znika z dashboardu. Ponowna eskalacja otwiera nowy epizod z nowym zegarem.
Nie każda anomalia to atak
Pamiętajmy jednak, że system nie żywi się wyłącznie incydentami bezpieczeństwa. Któregoś dnia dzienny koszt jednego z monitorowanych przez nas kont wzrósł 7,6-krotnie ponad normę. Alert wyszedł, zadanie w CRM trafiło do opiekuna, a ten w ciągu kilkunastu minut potwierdził z klientem, że to planowany trening modeli na Vertex AI. Epizod wyciszono jednym kliknięciem do końca dnia, zachowując eskalację krytycznych. Klient dostał pytanie „Czy to Wasze?” w godzinę po fakcie, zamiast faktury z niespodzianką po miesiącu. System, który nie umie szybko odróżnić oczekiwanego od złośliwego, uczy ludzi ignorowania alertów, a to prosta droga do powtórki z maja.
Detale, które bolą dopiero w produkcji
Liczby bez kontekstu kłamią
Ta sama kwota w jednym środowisku to szum, w innym incydent miesiąca. Próg porównywany z surową liczbą oznacza w praktyce „1000 czegokolwiek”. Dlatego każdą wartość osadzamy w kontekście konkretnego konta, jego skali i historii, a ludziom pokazujemy kwoty w postaci, w jakiej znają je z faktur.
Dane psują się po cichu
Dane billingowe z różnych źródeł i okresów potrafią różnić się układem i typami. Kod, który zakłada, że wszędzie jest tak samo, nie wywali się z żadnym błędem, tylko po cichu przestawi liczby. W systemie, któremu ludzie mają ufać, to najgorszy rodzaj błędu, więc żadne założenie o danych nie jest u nas domyślne.
Zmęczenie alertami to dług
Każdy zbędny alert obniża zaufanie do wszystkich następnych. Dlatego system ma cały zestaw bezpieczników przeciwko spamowi: deduplikację, cooldowny, wyciszanie i automatyczne wygaszanie epizodów. Projektujemy pod sygnał, nie pod wolumen.
Billing spływa z opóźnieniem
Eksport potrafi „dopełniać” dzień jeszcze wiele godzin. Lipcowa karta z 4:30 pokazywała kwotę kilkukrotnie niższą niż finalne zamknięcie tej doby, a licznik rósł długo po zablokowaniu ruchu. System musi o tym wiedzieć, bo inaczej brałby spływające z opóźnieniem dane za eskalację i budził ludzi bez powodu.
Jeden silnik, dwa tryby pracy
Ta sama architektura działa dziś w dwóch trybach.
Pierwszy to usługa zarządzana. Środowisko klienta obejmujemy naszym monitoringiem, alerty trafiają równolegle do naszego zespołu i do klienta, anomalię klasyfikuje i prowadzi konkretny opiekun, a tam, gdzie mamy nadane uprawnienia, reagujemy bezpośrednio zamiast tylko eskalować. Lipcowa historia pokazuje, gdzie przebiega ta granica: bez uprawnień nawet najlepsza detekcja kończy się na telefonie do właściciela środowiska.
Drugi tryb wynika wprost z konstrukcji. Skoro silnik jest bezstanowy i opiera się wyłącznie na standardowym eksporcie billingu do BigQuery, da się go postawić w dowolnym środowisku Google Cloud, także takim, którego na co dzień nie obsługujemy. W takim wdrożeniu zakres alertów zawężamy z całego konta billingowego do widoku projektów. Każdy zespół widzi anomalie w swoich projektach, z własnymi progami i kanałami powiadomień. Chat, e-mail czy webhook są z perspektywy systemu wymiennymi adapterami. Cała reszta, od okien SQL po cykl życia epizodu, działa w obu trybach identycznie.
Co z tego wynika dla Twojego środowiska
Niezależnie od tego, czy zbudujesz coś takiego samodzielnie, czy nie, kilka rzeczy przenosi się wprost. Baseline musi być behawioralny i liczony per konto, bo wspólny próg dla wszystkich środowisk to fikcja. Alert powinien mieć cykl życia, bo bez holdu, deduplikacji i autoclose system w kilka tygodni pożre własną wiarygodność. Statystyka tylko wykrywa, decyzję podejmuje kontekst, czyli mapowanie anomalii na klienta, opiekuna i konkretne usługi. No i sprawa najprostsza z najtrudniejszych: koszt to telemetria bezpieczeństwa. Finanse patrzą na niego raz w miesiącu, monitoring musi działać non stop.