Il sistema di bot-challenge di Cloudflare può interrompere silenziosamente l'invio di moduli HTML ordinari, trasformando un semplice clic per un pagamento in un vicolo cieco per gli utenti reali. Passare da una POST di navigazione nativa a un flusso "fetch-first" ripristina l'esperienza senza compromettere la sicurezza.
Perché il problema è importante
Uno sviluppatore ha rilasciato un modulo di pagamento che funzionava in ogni suite di test, con curl e sul server locale. Lo stesso modulo, quando un cliente usava Chrome, restituiva un errore di sicurezza dopo il primo clic e un messaggio di "timeout-or-duplicate" al secondo. Il fallimento ha costretto a tre rilasci di hot-fix e a un'intera giornata di debugging.
L'edge nascosto
Il modulo risiede in un pacchetto Astro open-source che si affida a un elemento <form> HTML semplice. Quando l'utente clicca su Pay, il server risponde con un redirect 303 verso Stripe, e il browser segue il redirect senza alcuno JavaScript. I siti utilizzano questo pattern come fallback quando gli script sono disabilitati.
Cloudflare si trova davanti al sito ed esegue un motore di rilevamento bot. Per le normali richieste GET, può mostrare una sfida intermedia (un CAPTCHA o un controllo JavaScript). Dopo che il browser supera la sfida, la richiesta procede.
Tuttavia, una POST di navigazione non può essere messa in pausa per una sfida e poi ripresa con il corpo intatto. L'edge scarta la richiesta e restituisce uno stato 503, lasciando il browser con una pagina bianca o un errore generico. I browser di test automatizzati, che portano lo stesso fingerprint su cui Cloudflare si fida, non attivano mai la sfida, quindi il problema rimane invisibile finché un utente reale non accede al sito.
Cosa hanno rivelato i log
Una traccia di rete live da una sessione Chrome di un utente ha mostrato due richieste contrastanti allo stesso endpoint:
- Navigation POST → risposta 503, scheda bloccata.
- fetch() POST → richiesta completata.
Entrambe le richieste originavano dalla stessa origine, portavano le stesse credenziali e avvenivano nello stesso momento. L'unica differenza era il metodo di trasporto. La richiesta fetch ha aggirato il flusso intermedio che blocca le POST di navigazione.
Percorsi che non hanno portato a nulla
Lo sviluppatore ha provato una serie di correzioni che non hanno colto la causa principale:
- Rigenerazione dei token Turnstile, assumendo che fossero scaduti.
- Inserimento di intervalli IP nella whitelist, pensando che il blocco fosse basato sulla posizione.
- Disattivazione delle estensioni, pulizia dei service worker e cancellazione dei cookie.
Ogni modifica ha lasciato l'errore invariato perché il fallimento originava a monte, all'edge, e non nel codice client o server.
La soluzione pragmatica
Invece di disattivare la protezione di Cloudflare, il modulo è stato riprogettato per utilizzare un pattern fetch-first:
- Raccogliere i dati del modulo e inviarli con
fetch()come payload JSON. - Gestire la risposta del server. Se il server restituisce un URL per il gateway di pagamento, invocare
location.assign()per navigare lì con una semplice richiesta GET.
Le richieste fetch non attivano la sfida intermedia, quindi la POST raggiunge il server di origine. Il successivo redirect GET può passare in sicurezza attraverso qualsiasi sfida, poiché i corpi GET sono vuoti e possono essere riprodotti dopo che l'utente ha superato la sfida.
Rischi per gli sviluppatori
- Fiducia dell'utente: un modulo di pagamento che fallisce silenziosamente erode la fiducia e può portare a una perdita di entrate.
- Costi di manutenzione: l'incidente ha richiesto tre rilasci di patch e un'intera giornata di indagine.
- Punti ciechi nei test: affidarsi esclusivamente agli ambienti di test interni può far sfuggire fallimenti in casi limite che appaiono solo nel mondo reale.
Lezioni per l'intera comunità
- Monitorare i browser reali. Quando un problema appare solo per gli utenti effettivi, cattura i log di rete da quelle sessioni invece di fidarti delle esecuzioni di test automatizzate.
- Considerare l'edge come parte dello stack. Cloudflare si trova tra il client e il server; il suo comportamento influenza il modo in cui le richieste devono essere strutturate.
- Scegliere il trasporto corretto. Le POST di navigazione e le POST fetch viaggiano attraverso percorsi diversi all'edge. Progetta le API tenendo presente questa distinzione.
- Esporre i dettagli degli errori. Mostra i messaggi 503 o "timeout-or-duplicate" all'interfaccia utente, in modo che gli sviluppatori possano vedere l'esatto tipo di fallimento senza dover scavare nei log.
Cosa monitorare in futuro
Gli sviluppatori dovrebbero sottoporre ad audit qualsiasi flusso di lavoro basato su moduli che si affida alla navigazione POST nativa, specialmente quando Cloudflare o servizi di sicurezza CDN simili si trovano davanti al sito. L'aggiunta di un leggero wrapper fetch può prevenire fallimenti simili. Gli strumenti di monitoraggio che catturano i codici di stato generati dall'edge segnaleranno il problema prima che raggiunga i clienti.
In sintesi: Quando le sfide bot di Cloudflare sono attive, l'invio di un semplice modulo HTML è vulnerabile a un fallimento silenzioso. Reindirizzare la POST tramite fetch() e completare il flusso con un reindirizzamento GET permette di aggirare il limite dell'edge mantenendo intatta la sicurezza. Considera l'edge come codice, non solo come un salto di rete, e progetta i tuoi trasporti di conseguenza.
