5 grudnia 2025 r. o godz. 08:47 UTC (wszystkie podane godziny odnoszą się do UTC) część sieci Cloudflare zaczęła doświadczać poważnych awarii. Incydent został rozwiązany o 09:12 (łączny czas trwania ok. 25 minut), gdy wszystkie usługi zostały w pełni przywrócone.
Skutki incydentu odczuła część klientów, co stanowiło około 28 procent całego ruchu HTTP obsługiwanego przez Cloudflare. Aby konkretny klient odczuł skutki incydentu, musiało jednocześnie zaistnieć kilka czynników, jak opisano poniżej.
Problem nie został spowodowany, bezpośrednio ani pośrednio, cyberatakiem na systemy Cloudflare ani żadną inną złośliwą aktywnością. Zamiast tego został wywołany przez zmiany wprowadzane w naszej logice przetwarzania treści żądań podczas próby wykrycia i ograniczenia ujawnionej w tym tygodniu luki w komponentach serwerowych React.
Jakakolwiek przerwa w działaniu naszych systemów jest nieakceptowalna i zdajemy sobie sprawę, że po incydencie z 18 listopada ponownie zawiedliśmy użytkowników Internetu. W przyszłym tygodniu opublikujemy szczegółowe informacje o naszych działaniach mających na celu zapobieganie tego typu incydentom.
Poniższy wykres przedstawia błędy HTTP 500 obsługiwane przez naszą sieć w czasie incydentu (czerwona linia u dołu), w porównaniu z niezakłóconym całkowitym ruchem w sieci Cloudflare (zielona linia u góry).
Zapora aplikacji internetowych (WAF) Cloudflare zapewnia klientom ochronę przed złośliwymi payloadami, umożliwiając ich wykrywanie i blokowanie. W tym celu serwer proxy Cloudflare buforuje zawartość treści żądania HTTP w pamięci na potrzeby analizy. Przed dzisiejszym dniem rozmiar bufora był ustawiony na 128 KB.
W ramach naszych bieżących działań mających na celu ochronę klientów korzystających z React przed krytyczną luką w zabezpieczeniach, CVE-2025-55182, rozpoczęliśmy zwiększanie rozmiaru bufora do 1 MB, tj. domyślnego limitu dozwolonego przez aplikacje Next.js, aby zapewnić ochronę jak największej liczbie klientów.
Ta pierwsza zmiana była wdrażana przy użyciu naszego systemu stopniowego wdrażania. Podczas wdrażania zauważyliśmy, że nasze wewnętrzne narzędzie do testowania WAF nie obsługiwało zwiększonego rozmiaru bufora. Ponieważ to wewnętrzne narzędzie testowe nie było wówczas potrzebne i nie miało wpływu na ruch klientów, wprowadziliśmy drugą zmianę, aby je wyłączyć.
Ta druga zmiana, polegająca na wyłączeniu naszego narzędzia testowego WAF, została wdrożona za pomocą naszego globalnego systemu konfiguracji. Ten system nie przeprowadza stopniowego wdrażania, lecz propaguje zmiany w ciągu kilku sekund do całej floty serwerów w naszej sieci i jest obecnie poddawany przeglądowi w następstwie awarii, której doświadczyliśmy 18 listopada.
Niestety, w naszej wersji FL1 serwera proxy, w pewnych okolicznościach ta druga zmiana polegająca na wyłączeniu narzędzia do testowania reguł WAF spowodowała stan błędu, w wyniku którego nasza sieć zaczęła zwracać kody błędów HTTP 500.
Gdy tylko zmiana została rozpropagowana w naszej sieci, wykonanie kodu w naszym serwerze proxy FL1 natrafiło na błąd w module reguł, co doprowadziło do następującego wyjątku LUA:
[lua] Failed to run module rulesets callback late_routing: /usr/local/nginx-fl/lua/modules/init.lua:314: attempt to index field 'execute' (a nil value)
co powoduje generowanie błędów HTTP o kodzie 500.
Problem zidentyfikowano krótko po wprowadzeniu zmiany, którą wycofano o 09:12, po czym cały ruch był obsługiwany prawidłowo.
Zdarzenie dotyczyło klientów, których zasoby internetowe były obsługiwane przez nasz starszy serwer proxy FL1 ORAZ którzy mieli wdrożony zestaw reguł zarządzanych Cloudflare. Wszystkie żądania dotyczące witryn internetowych w tym stanie zwracały błąd HTTP 500, z niewielkim wyjątkiem niektórych testowych punktów końcowych, takich jak /cdn-cgi/trace.
Klienci, u których nie zastosowano powyższej konfiguracji, nie odczuli żadnych skutków. Zdarzenie nie miało wpływu także na ruch klientów obsługiwany przez naszą sieć China Network.
System zestawów reguł Cloudflare składa się z zestawów reguł, które są oceniane dla każdego żądania trafiającego do naszego systemu. Reguła składa się z filtra, który wybiera określony ruch, oraz działania, które stosuje wobec tego ruchu odpowiedni efekt. Typowe działania to „block”, „log” lub „skip”. Innym typem działania jest „execute”, które służy do wywoływania oceny innego zestawu reguł.
Nasz wewnętrzny system rejestrowania wykorzystuje tę funkcję do oceny nowych reguł, zanim udostępnimy je publicznie. Zestaw reguł najwyższego poziomu wykonuje inny zestaw reguł zawierający reguły testowe. To właśnie te reguły testowe próbowaliśmy wyłączyć.
W ramach systemu zestawów reguł posiadamy podsystem wyłącznika awaryjnego (killswitch), którego celem jest umożliwienie szybkiego wyłączenia reguły działającej nieprawidłowo. Ten system wyłącznika awaryjnego otrzymuje informacje z naszego globalnego systemu konfiguracji, o którym wspomniano w poprzednich sekcjach. W przeszłości wielokrotnie korzystaliśmy z tego systemu wyłącznika awaryjnego w celu łagodzenia skutków incydentów oraz dysponujemy jasno zdefiniowaną standardową procedurą operacyjną, którą zastosowano również w przypadku tego incydentu.
Jednak nigdy wcześniej nie zastosowaliśmy wyłącznika awaryjnego do reguły z działaniem „execute”. Gdy zastosowano wyłącznik awaryjny, kod prawidłowo pominął ocenę działania „execute” i nie przeprowadził oceny podzestawu reguł, na który to działanie wskazywało. Wystąpił jednak błąd podczas przetwarzania ogólnych wyników oceny zestawu reguł:
if rule_result.action == "execute" then
rule_result.execute.results = ruleset_results[tonumber(rule_result.execute.results_index)]
end
Kod ten zakłada, że jeśli zestaw reguł ma ustawiony parametr action="execute", to obiekt „rule_result.execute” będzie istniał. Ponieważ reguła została pominięta, obiekt rule_result.execute nie istniał, a skrypt Lua zwrócił błąd z powodu próby odczytu wartości z pola nieistniejącego obiektu (nil).
To prosty błąd w kodzie, który przez wiele lat pozostawał niewykryty. Tego typu błędom zapobiegają języki o silnych systemach typów. W naszym nowym serwerze proxy FL2, w którym zastąpiono ten fragment kodu i który został napisany w języku Rust, błąd ten nie wystąpił.
A co ze zmianami wprowadzonymi po incydencie z 18 listopada 2025 r.?
Wprowadziliśmy niepowiązaną zmianę, która spowodowała podobny dłuższy incydent dostępności dwa tygodnie temu, 18 listopada 2025 roku. W obu przypadkach wdrożenie mające na celu złagodzenie problemu bezpieczeństwa u naszych klientów zostało rozpropagowane w całej naszej sieci i doprowadziło do wystąpienia błędów u niemal wszystkich naszych klientów.
Po tym incydencie rozmawialiśmy bezpośrednio z setkami klientów i podzieliliśmy się naszymi planami wprowadzenia zmian mających na celu zapobieganie sytuacjom, w których pojedyncze aktualizacje powodują tak rozległe skutki. Uważamy, że zmiany te pomogłyby zapobiec skutkom dzisiejszego incydentu, ale niestety nie wdrożyliśmy ich jeszcze w całości.
Wiemy, że rozczarowujące jest to, iż prace te nie zostały jeszcze ukończone. Pozostaje to naszym najwyższym priorytetem w całej organizacji. W szczególności poniższe projekty powinny pomóc ograniczyć wpływ tego rodzaju zmian:
Ulepszone wdrażanie i wersjonowanie — podobnie jak w przypadku stopniowego wdrażania oprogramowania z rygorystyczną walidacją kondycji systemu, dane wykorzystywane do szybkiego reagowania na zagrożenia oraz ogólnej konfiguracji muszą mieć te same funkcje bezpieczeństwa i ograniczania zasięgu skutków awarii. Obejmuje to między innymi walidację kondycji systemu oraz możliwość szybkiego wycofania zmian.
Usprawnione mechanizmy awaryjne (break glass) — zapewnienie możliwości wykonania operacji krytycznych nawet w obliczu dodatkowych typów awarii. Dotyczy to zarówno usług wewnętrznych, jak i wszystkich standardowych metod interakcji z płaszczyzną zarządzania Cloudflare, z których korzystają wszyscy klienci Cloudflare.
Obsługa błędów typu „fail-open” — w ramach działań na rzecz zwiększenia odporności zastępujemy nieprawidłowo zastosowaną logikę twardego błędu (hard-fail) we wszystkich krytycznych komponentach płaszczyzny danych Cloudflare. Jeśli plik konfiguracyjny jest uszkodzony lub zawiera wartości spoza dopuszczalnego zakresu (np. przekracza limity funkcji), system zarejestruje błąd i przejdzie do znanego, poprawnego stanu albo przepuści ruch bez wykonywania oceny, zamiast odrzucać żądania. Niektóre usługi będą prawdopodobnie oferować klientom możliwość wyboru trybu fail-open lub fail-closed w określonych scenariuszach. Będzie to obejmować mechanizmy zapobiegania dryfowi konfiguracji, które zapewnią ciągłe egzekwowanie tych ustawień.
Przed końcem przyszłego tygodnia opublikujemy szczegółowe zestawienie wszystkich realizowanych projektów dotyczących zwiększenia odporności, w tym wymienionych powyżej. W trakcie trwania tych prac blokujemy wprowadzanie wszelkich zmian w naszej sieci, aby zapewnić lepsze mechanizmy łagodzenia skutków i wycofywania zmian przed ponownym rozpoczęciem wdrożeń.
Tego rodzaju incydenty, a także fakt, że występują w tak niewielkich odstępach czasu, są niedopuszczalne w przypadku takiej sieci jak nasza. W imieniu zespołu Cloudflare chcemy przeprosić za skutki i niedogodności, które sytuacja ta ponownie spowodowała dla naszych klientów oraz dla całego Internetu.
Czas (UTC) | Status | Opis |
08:47 | Początek INCYDENTU | Wdrożono zmianę konfiguracji i rozpropagowano ją w sieci |
08:48 | Pełen wpływ | Pełna propagacja zmiany |
08:50 | Zgłoszono INCYDENT | Automatyczne alerty |
09:11 | Wycofano zmianę | Wycofano zmianę konfiguracji i rozpoczęto jej propagację |
09:12 | Koniec INCYDENTU | Wycofanie w pełni rozpropagowane, cały ruch przywrócony |