O sistema de desafio de bots da Cloudflare pode interromper silenciosamente envios de formulários HTML comuns, transformando um simples clique de pagamento em um beco sem saída para usuários reais. Mudar a requisição de um POST de navegação nativo para um fluxo baseado em fetch restaura a experiência sem comprometer a segurança.
Por que o problema é importante
Um desenvolvedor lançou um formulário de pagamento que funcionava em todos os conjuntos de testes, com curl e no servidor local. O mesmo formulário, quando um cliente usava o Chrome, apresentava um erro de segurança após o primeiro clique e uma mensagem de “timeout-or-duplicate” no segundo. A falha forçou três lançamentos de correções rápidas (hot-fixes) e um dia inteiro de depuração.
O edge oculto
O formulário reside em um pacote Astro de código aberto que depende de um elemento <form> HTML puro. Quando o usuário clica em Pay, o servidor responde com um redirecionamento 303 para o Stripe, e o navegador segue o redirecionamento sem qualquer JavaScript. Sites usam esse padrão como um fallback para quando os scripts estão desativados.
A Cloudflare fica à frente do site e executa um mecanismo de detecção de bots. Para requisições GET comuns, ela pode exibir um desafio intersticial (um CAPTCHA ou uma verificação de JavaScript). Após o navegador passar pelo desafio, a requisição prossegue.
Um POST de navegação, no entanto, não pode ser pausado para um desafio e depois retomado com seu corpo (body) intacto. O edge descarta a requisição e retorna um status 503, deixando o navegador com uma página em branco ou um erro genérico. Navegadores de testes automatizados, que carregam a mesma impressão digital (fingerprint) que a Cloudflare confia, nunca acionam o desafio, portanto, o problema permanece invisível até que um usuário real acesse o site.
O que os logs revelaram
Um rastreamento de rede em tempo real de uma sessão do Chrome de um usuário mostrou duas requisições contrastantes para o mesmo endpoint:
- Navigation POST → resposta 503, aba travada.
- fetch() POST → requisição concluída.
Ambas as requisições originaram-se da mesma origem, carregaram as mesmas credenciais e ocorreram no mesmo momento. A única diferença foi o método de transporte. A requisição fetch contornou o fluxo intersticial que bloqueia os POSTs de navegação.
Caminhos que não levaram a lugar nenhum
O desenvolvedor tentou uma série de correções que não atingiram a causa raiz:
- Renovação de tokens Turnstile, assumindo que haviam expirado.
- Inclusão de intervalos de IP na whitelist, pensando que o bloqueio era baseado em localização.
- Desativação de extensões, limpeza de service workers e exclusão de cookies.
Cada mudança deixou o erro inalterado porque a falha se originava upstream no edge, não no código do cliente ou do servidor.
A correção pragmática
Em vez de desativar a proteção da Cloudflare, o formulário foi reestruturado para usar um padrão baseado em fetch:
- Coletar os dados do formulário e enviá-los com
fetch()como um payload JSON. - Lidar com a resposta do servidor. Se o servidor retornar uma URL para o gateway de pagamento, invoque
location.assign()para navegar até lá com uma requisição GET simples.
As requisições fetch não acionam o desafio intersticial, portanto, o POST chega ao servidor de origem. O redirecionamento GET subsequente pode passar com segurança por qualquer desafio, já que os corpos (bodies) de requisições GET são vazios e podem ser repetidos após o usuário superar o desafio.
Riscos para desenvolvedores
- Confiança do usuário: Um formulário de pagamento que falha silenciosamente corrói a confiança e pode levar à perda de receita.
- Sobrecarga de manutenção: O incidente exigiu três lançamentos de patches e um dia inteiro de investigação.
- Pontos cegos de teste: Confiar apenas em ambientes de teste internos pode fazer com que falhas de casos extremos (edge cases) passem despercebidas, aparecendo apenas no mundo real.
Lições para a comunidade em geral
- Instrumente navegadores reais. Quando um problema aparece apenas para usuários reais, capture os logs de rede dessas sessões em vez de confiar apenas em execuções de testes automatizados.
- Trate o edge como parte da stack. A Cloudflare fica entre o cliente e o servidor; seu comportamento influencia como as requisições devem ser estruturadas.
- Escolha o transporte correto. POSTs de navegação e POSTs via
fetchviajam por caminhos diferentes no edge. Projete APIs com essa distinção em mente. - Exponha detalhes de erro. Exiba as mensagens 503 ou “timeout-or-duplicate” na interface do usuário (UI) para que os desenvolvedores possam ver o modo exato de falha sem precisar vasculhar logs.
O que observar a seguir
Desenvolvedores devem auditar quaisquer fluxos de trabalho baseados em formulários que dependam de navegação POST nativa, especialmente quando a Cloudflare ou serviços de segurança de CDN semelhantes estiverem à frente do site. Adicionar um wrapper de fetch leve pode prevenir falhas semelhantes. Ferramentas de monitoramento que capturam códigos de status gerados no edge sinalizarão o problema antes que ele chegue aos clientes.
Conclusão: Quando os desafios de bot da Cloudflare estão ativos, o envio de um formulário HTML simples é vulnerável a falhas silenciosas. Redirecionar o POST através do fetch() e completar o fluxo com um redirecionamento GET contorna a limitação da edge, mantendo a segurança intacta. Trate a edge como código, não apenas como um salto de rede, e projete seus transportes de acordo.
