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:

  1. Coletar os dados do formulário e enviá-los com fetch() como um payload JSON.
  2. 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 fetch viajam 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.