Sistem bot-challenge Cloudflare dapat secara diam-diam mematikan pengiriman formulir HTML biasa, mengubah klik pembayaran sederhana menjadi jalan buntu bagi pengguna asli. Mengalihkan permintaan dari POST navigasi asli ke alur fetch-first dapat memulihkan pengalaman tersebut tanpa mengorbankan keamanan.

Mengapa masalah ini penting

Seorang pengembang merilis formulir pembayaran yang berfungsi di setiap rangkaian pengujian, dengan curl, dan di server lokal. Formulir yang sama, ketika pelanggan menggunakan Chrome, memunculkan kesalahan keamanan setelah klik pertama dan pesan “timeout-or-duplicate” pada klik kedua. Kegagalan ini memaksa tiga rilis hot-fix dan satu hari penuh proses debugging.

Sisi tersembunyi di edge

Formulir tersebut berada dalam paket Astro open-source yang mengandalkan elemen <form> HTML biasa. Saat pengguna mengklik Pay, server membalas dengan pengalihan (redirect) 303 ke Stripe, dan browser mengikuti pengalihan tersebut tanpa JavaScript apa pun. Situs web menggunakan pola ini sebagai cadangan (fallback) ketika skrip dinonaktifkan.

Cloudflare berada di depan situs dan menjalankan mesin deteksi bot. Untuk permintaan GET biasa, ia dapat menampilkan tantangan interstitial (CAPTCHA atau pemeriksaan JavaScript). Setelah browser melewati tantangan tersebut, permintaan akan berlanjut.

Namun, sebuah POST navigasi tidak dapat dihentikan sejenak untuk tantangan dan kemudian dilanjutkan dengan body yang tetap utuh. Edge akan membuang permintaan tersebut dan mengembalikan status 503, meninggalkan browser dengan halaman kosong atau kesalahan umum. Browser pengujian otomatis, yang membawa fingerprint yang sama dengan yang dipercaya Cloudflare, tidak pernah memicu tantangan tersebut, sehingga masalah ini tetap tidak terlihat sampai pengguna asli mengunjungi situs tersebut.

Apa yang diungkapkan oleh log

Jejak jaringan langsung dari sesi Chrome pengguna menunjukkan dua permintaan yang kontras ke endpoint yang sama:

  • Navigation POST → respons 503, tab macet.
  • fetch() POST → permintaan selesai.

Kedua permintaan berasal dari origin yang sama, membawa kredensial yang sama, dan terjadi pada saat yang sama. Satu-satunya perbedaan adalah metode transportasinya. Permintaan fetch melewati alur interstitial yang memblokir POST navigasi.

Jalur yang tidak membuahkan hasil

Pengembang mencoba serangkaian perbaikan yang meleset dari akar masalah:

  • Memperbarui token Turnstile, dengan asumsi token telah kedaluwarsa.
  • Memasukkan rentang IP ke dalam whitelist, mengira pemblokiran berbasis lokasi.
  • Menonaktifkan ekstensi, membersihkan service worker, dan menghapus cookie.

Setiap perubahan tidak mengubah kesalahan karena kegagalan berasal dari upstream di edge, bukan di kode client atau server.

Perbaikan pragmatis

Alih-alih mematikan perlindungan Cloudflare, formulir tersebut dirancang ulang untuk menggunakan pola fetch-first:

  1. Kumpulkan data formulir dan kirimkan dengan fetch() sebagai payload JSON.
  2. Tangani respons server. Jika server mengembalikan URL untuk gateway pembayaran, panggil location.assign() untuk menavigasi ke sana dengan permintaan GET sederhana.

Permintaan fetch tidak memicu tantangan interstitial, sehingga POST mencapai server origin. Pengalihan GET berikutnya dapat melewati tantangan apa pun dengan aman, karena body GET kosong dan dapat dimainkan ulang setelah pengguna menyelesaikan tantangan.

Risiko bagi pengembang

  • Kepercayaan pengguna: Formulir pembayaran yang gagal secara diam-diam mengikis kepercayaan dan dapat menyebabkan hilangnya pendapatan.
  • Beban pemeliharaan: Insiden ini memerlukan tiga rilis patch dan satu hari penuh investigasi.
  • Titik buta pengujian: Mengandalkan lingkungan pengujian internal saja dapat melewatkan kegagalan edge-case yang hanya muncul di dunia nyata.

Pelajaran bagi komunitas luas

  • Lakukan instrumentasi pada browser asli. Ketika masalah hanya muncul bagi pengguna asli, tangkap log jaringan dari sesi tersebut alih-alih hanya mempercayai hasil pengujian otomatis.
  • Anggap edge sebagai bagian dari stack. Cloudflare berada di antara client dan server; perilakunya memengaruhi bagaimana permintaan harus disusun.
  • Pilih transport yang tepat. POST navigasi dan POST fetch melewati jalur yang berbeda di edge. Rancang API dengan mempertimbangkan perbedaan tersebut.
  • Tampilkan detail kesalahan. Munculkan pesan 503 atau “timeout-or-duplicate” ke UI agar pengembang dapat melihat mode kegagalan yang tepat tanpa harus menggali log.

Apa yang perlu diwaspadai selanjutnya

Pengembang harus mengaudit alur kerja berbasis formulir apa pun yang mengandalkan navigasi POST asli, terutama ketika Cloudflare atau layanan keamanan CDN serupa berada di depan situs. Menambahkan wrapper fetch yang ringan dapat mencegah kegagalan serupa. Alat pemantauan yang menangkap kode status yang dihasilkan oleh edge akan menandai masalah sebelum mencapai pelanggan.

Kesimpulan: Saat tantangan bot Cloudflare aktif, pengiriman formulir HTML biasa rentan terhadap kegagalan senyap. Mengalihkan rute POST melalui fetch() dan menyelesaikan alur dengan pengalihan GET dapat menghindari keterbatasan edge sambil tetap menjaga keamanan. Perlakukan edge sebagai kode, bukan sekadar lompatan jaringan, dan rancang transport Anda sebagaimana mestinya.