Skip to content

Download público de CSV: captcha aceito na tela 1 e recusado na tela 2 (regressão do #1054) #1061

Description

@rpgmem

Encontrado em smoke test do develop (testes), após o merge do #1054.

Sintoma

Na página do shortcode [ffc_csv_download]:

  1. O visitante responde o captcha matemático → aceito, avança para a tela de detalhes do formulário.
  2. Clica em "Baixar CSV" → "Erro: A resposta matemática está incorreta."

O erro impede o download. Do ponto de vista do usuário é um contrassenso: a mesma resposta foi aceita um passo antes.

Causa

O download público é um fluxo de duas requisições que valida o mesmo captcha duas vezes:

Etapa Handler Site
1 — tela de detalhes PublicCsvDownload::ajax_info() class-ffc-public-csv-download.php:442
2 — download PublicFormsExportSource::authorize_start() class-ffc-public-forms-export-source.php:114

Antes do #1054 isso era inofensivo: o token era replayável, então validar duas vezes saía de graça. O #1054 tornou o token de uso único (ChallengeStore::redeem()), e a etapa 1 passou a queimá-lo. A etapa 2 recebe exatamente o mesmo ffc_captcha_hashffc-csv-download-flow.js:41 envia startData: api.formData, a string produzida por $form.serialize() na etapa 1 — e encontra o token já gasto.

Não afeta os outros consumidores do captcha: envio de formulário, verificação de certificado e agendamento são de requisição única, validam e consomem no mesmo request. O caminho sem JS do próprio CSV (handle_request(), via admin-post.php) também é de requisição única e está correto.

Correção

Separar conferir de gastar. O captcha passa a ser consumido pela ação que ele autoriza, não pela leitura de metadados que a antecede:

  • ChallengeStore::is_spent() — metade só-leitura do ledger.
  • SecurityService::peek_simple_captcha() / peek_security_fields() — conferem sem resgatar. Um token já gasto é recusado aqui também, senão a contradição só andaria um passo adiante.
  • CaptchaProviderInterface::peek() — no contrato, não na implementação: toda estratégia que vale a pena é de uso único (uma solução de proof-of-work é replayável até o servidor registrá-la, exatamente como o token matemático), então o ALTCHA vai precisar disso igualmente.
  • ajax_info() confere; authorize_start() e handle_request() consomem.

A propriedade fechada pelo #1054 fica intacta: um par (resposta, token) capturado continua valendo um download — o mesmo que vale para um visitante legítimo.

Cobertura

Guardas no nível da unidade (o ledger e o provider) e no nível do handler — ajax_info() chama peek_security_fields() e nunca validate_security_fields(). Verifiquei que a guarda falha quando o código regride.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions