System wyzwań dla botów Cloudflare może po cichu uniemożliwiać przesyłanie zwykłych formularzy HTML, zmieniając proste kliknięcie płatności w ślepą uliczkę dla prawdziwych użytkowników. Zmiana żądania z natywnego POST nawigacyjnego na przepływ typu „fetch-first” przywraca płynność działania bez kompromisów w kwestii bezpieczeństwa.

Dlaczego ten problem jest istotny

Programista udostępnił formularz płatności, który działał w każdym zestawie testowym, przy użyciu curl oraz na lokalnym serwerze. Ten sam formularz, gdy klient używał Chrome, wyrzucał błąd bezpieczeństwa po pierwszym kliknięciu i komunikat „timeout-or-duplicate” przy drugim. Awaria wymusiła trzy wydania typu hot-fix i cały dzień debugowania.

Ukryty problem na warstwie edge

Formularz znajduje się w pakiecie open-source Astro, który opiera się na zwykłym elemencie HTML <form>. Gdy użytkownik klika Pay, serwer odpowiada przekierowaniem 303 do Stripe, a przeglądarka podąża za przekierowaniem bez użycia JavaScriptu. Strony stosują ten wzorzec jako rozwiązanie zapasowe (fallback) na wypadek, gdy skrypty są wyłączone.

Cloudflare znajduje się przed witryną i uruchamia silnik wykrywania botów. Dla zwykłych żądań GET może wyświetlić wyzwanie pośrednie (interstitial challenge), takie jak CAPTCHA lub sprawdzenie JavaScript. Po przejściu weryfikacji przez przeglądarkę, żądanie jest kontynuowane.

Jednak POST nawigacyjny nie może zostać wstrzymany w celu przeprowadzenia weryfikacji, a następnie wznowiony z zachowaniem nienaruszonego ciała żądania. Warstwa edge odrzuca żądanie i zwraca status 503, pozostawiając przeglądarkę z pustą stroną lub ogólnym błędem. Zautomatyzowane przeglądarki testowe, które posiadają ten sam fingerprint, któremu Cloudflare ufa, nigdy nie wyzwalają weryfikacji, więc problem pozostaje niewidoczny, dopóki prawdziwy użytkownik nie odwiedzi strony.

Co wykazały logi

Rzeczywisty ślad sieciowy z sesji Chrome użytkownika wykazał dwa kontrastujące żądania do tego samego endpointu:

  • POST nawigacyjny → odpowiedź 503, karta zawiesiła się.
  • POST przez fetch() → żądanie zakończone sukcesem.

Oba żądania pochodziły z tego samego origin, zawierały te same poświadczenia i wystąpiły w tym samym momencie. Jedyną różnicą była metoda transportu. Żądanie fetch ominęło przepływ pośredni, który blokuje POSTy nawigacyjne.

Ścieżki, które do niczego nie prowadziły

Programista próbował serii poprawek, które nie dotyczyły przyczyny źródłowej:

  • Odnawiał tokeny Turnstile, zakładając, że wygasły.
  • Dodawał zakresy IP do białej listy, myśląc, że blokada wynika z lokalizacji.
  • Wyłączał rozszerzenia, czyścił service workery i usuwał pliki cookie.

Każda zmiana nie przynosiła rezultatu, ponieważ awaria występowała wyżej w stosie, na warstwie edge, a nie w kodzie klienta lub serwera.

Pragmatyczne rozwiązanie

Zamiast wyłączać ochronę Cloudflare, formularz został przeprojektowany tak, aby korzystać ze wzorca fetch-first:

  1. Zbierz dane formularza i wyślij je za pomocą fetch() jako payload JSON.
  2. Obsłuż odpowiedź serwera. Jeśli serwer zwróci adres URL bramki płatniczej, wywołaj location.assign(), aby przejść tam za pomocą prostego żądania GET.

Żądania fetch nie wyzwalają wyzwania pośredniego, dzięki czemu POST dociera do serwera origin. Następujące po nim przekierowanie GET może bezpiecznie przejść przez wszelkie weryfikacje, ponieważ ciała żądań GET są puste i mogą zostać powtórzone po tym, jak użytkownik przejdzie weryfikację.

Co to oznacza dla programistów

  • Zaufanie użytkowników: Formularz płatności, który po cichu zawodzi, niszczy zaufanie i może prowadzić do utraty przychodów.
  • Koszty utrzymania: Incydent wymagał trzech poprawek i całego dnia dochodzenia.
  • Martwe pola w testowaniu: Poleganie wyłącznie na wewnętrznych środowiskach testowych może sprawić, że przeoczone zostaną awarie przypadków brzegowych, które pojawiają się dopiero w rzeczywistym środowisku.

Lekcje dla szerszej społeczności

  • Wykorzystuj rzeczywiste przeglądarki do zbierania danych. Gdy problem pojawia się tylko u rzeczywistych użytkowników, przechwytuj logi sieciowe z tych sesji, zamiast ufać wyłącznie zautomatyzowanym testom.
  • Traktuj edge jako część stosu technologicznego. Cloudflare znajduje się między klientem a serwerem; jego zachowanie wpływa na to, jak muszą być strukturyzowane żądania.
  • Wybierz odpowiedni transport. POSTy nawigacyjne i POSTy przez fetch podróżują różnymi ścieżkami na warstwie edge. Projektuj API z uwzględnieniem tej różnicy.
  • Wyświetlaj szczegóły błędów. Wyprowadzaj komunikaty 503 lub „timeout-or-duplicate” do interfejsu użytkownika, aby programiści mogli zobaczyć dokładny tryb awarii bez przeszukiwania logów.

Na co zwrócić uwagę w przyszłości

Programiści powinni przeprowadzić audyt wszystkich procesów opartych na formularzach, które polegają na natywnej nawigacji POST, zwłaszcza gdy przed witryną znajduje się Cloudflare lub podobne usługi bezpieczeństwa CDN. Dodanie lekkiego wrappera fetch może zapobiec podobnym awariom. Narzędzia monitorujące, które przechwytują kody statusu generowane na brzegu sieci (edge), pozwolą wykryć problem, zanim dotrze on do klientów.

Wniosek: Gdy aktywne są wyzwania botów Cloudflare, zwykłe przesyłanie formularza HTML jest podatne na cichą awarię. Przekierowanie żądania POST za pomocą fetch() i zakończenie procesu przekierowaniem GET pozwala obejść ograniczenie warstwy edge, zachowując jednocześnie pełne bezpieczeństwo. Traktuj edge jako kod, a nie tylko skok sieciowy, i projektuj swoje mechanizmy transportowe w odpowiedni sposób.