O corpo completo de uma publicação na fila permanece na fila de revisão mesmo após o autor excluí-la, sem forma de removê-la

Resumo

Quando uma publicação passa pela fila de aprovação, ReviewableQueuedPost.payload["raw"] contém uma cópia completa do corpo submetido. Após a publicação ser aprovada e depois excluída pelo seu autor, a publicação pública é substituída pelo stub “(publicação excluída pelo autor)”, mas o item de revisão (reviewable) mantém o corpo original por todo o tempo que a linha da publicação existir — o que, após uma exclusão pelo autor, é indefinidamente — e continua servindo-o à equipe em /review.

Não há um caminho de limpeza (scrub) para isso: Reviewable.scrubbable_types é [ReviewableUser], portanto a ação de limpeza para administradores adicionada em #36556 (“moderadores agora podem usar uma ação ‘Scrub’ para remover os dados pessoais do usuário”) não cobre publicações enfileiradas. O payload é removido apenas se alguém destruir permanentemente o registro da publicação, o que não é o que a exclusão pelo autor faz e o que a interface do usuário não permite para a primeira publicação de um tópico.

Reproduzido em v2026.7.1. O código relevante não foi alterado no main até 2026-09-25.

Versão

Discourse v2026.7.1 (auto-hospedado, instância local de teste autorizada). Apenas o núcleo — nenhum plugin envolvido.

Passos para reproduzir

  1. Como administrador, configure para que as publicações de um usuário comum exijam aprovação, por exemplo, defina approve post count para 5 e remova trust_level_0 de approve unless allowed groups.

  2. Como esse usuário comum, crie um tópico com um marcador único no corpo. A resposta retorna como {"action": "enqueued"}.

  3. Como membro da equipe, aprove-o: PUT /review/<reviewable_id>/perform/approve_post?version=0. A publicação é criada.

  4. Como o autor, exclua-a através da ação normal voltada para o usuário — DELETE /t/<topic_id>.json para o tópico cuja primeira publicação esta seja.

  5. Como membro da equipe, solicite GET /review.json?status=all.

Esperado

Assim que o autor exclui o conteúdo, a cópia de moderação do mesmo texto deve seguir o ciclo de vida do conteúdo: ela deve ser descartada, limpa (scrubbed) ou, pelo menos, ser limpável por um administrador da mesma forma que um ReviewableUser rejeitado.

Real

O passo 5 ainda retorna o corpo original completo. Medido na execução reproduzida:

Observação Resultado
Linha de posts após a exclusão pelo autor user_deleted = t, raw = '(tópico excluído pelo autor)'
reviewables.payload após a exclusão pelo autor {"raw":"Corpo enfileirado DRLGQUEUEDfb08ab5b648f. padding …"} — inalterado
GET /review.json?status=all da equipe contém o corpo original
Reviewable.scrubbable_types ["ReviewableUser"]
PUT /review/<id>/scrub.json do administrador HTTP 404 (não é um tipo limpável)
Após o registro da publicação ser destruído permanentemente a linha do reviewable é removida (dependent: :destroy)

A última linha é o único caminho que a limpa, e não é acessível a partir da exclusão pelo autor: a exclusão do autor marca a publicação como user_deleted e, após delete_removed_posts_after horas, move-a para a lixeira. Uma linha de publicação na lixeira continua existindo, portanto o payload continua existindo.

Origem do problema

  • app/models/reviewable_queued_post.rb constrói e lê payload['raw']; nada declara um propósito ou tempo de retenção para ele.

  • lib/post_destroyer.rb:562 resolve_reviewables_for_author_deletion apenas toca em Reviewable.where(target: @post, status: pending). Um reviewable de publicação enfileirada aprovada nunca é transicionado ou limpo.

  • app/models/post.rb:71 has_many :reviewables, as: :target, dependent: :destroy — dispara na destruição do registro, não na exclusão suave (soft delete) que uma exclusão pelo autor produz.

  • app/models/reviewable.rb:81 scrubbable_types retorna [ReviewableUser]; app/controllers/reviewables_controller.rb:191 adicionalmente exige status: rejected.

  • Não há limpeza agendada para reviewables. app/jobs/scheduled/ contém jobs clean_up_* / purge_* para rascunhos, tokens de e-mail, exportações, uploads, chaves de API e mais, mas nada para reviewables.

Impacto

Isso não é uma divulgação pública — a fila de revisão é exclusiva para a equipe, e não estou alegando o contrário. É uma lacuna de retenção de dados: um texto que um usuário removeu do fórum permanece no armazenamento de moderação indefinidamente, é exibido na interface da fila de revisão e nenhuma ação de administrador pode removê-lo, exceto destruindo permanentemente o registro da publicação.

Isso é importante pela mesma razão pela qual o #36556 adicionou limpeza para usuários rejeitados: payloads de reviewable podem conter dados pessoais, e um site que recebe uma solicitação de exclusão não tem uma maneira suportada de limpar um payload de publicação enfileirada.

Correção sugerida

Qualquer uma das seguintes opções resolve o problema; fazer ambas é melhor:

  1. Estender resolve_reviewables_for_author_deletion (e o caminho de lixeira) para limpar payload['raw'] em reviewables cuja publicação alvo tenha sido excluída pelo autor ou movida para a lixeira.

  2. Adicionar ReviewableQueuedPost a Reviewable.scrubbable_types e permitir que a ação de limpeza existente do administrador seja aplicada quando a publicação alvo não carrega mais o conteúdo, para que os operadores tenham um recurso suportado para payloads criados antes da correção.

Uma especificação de regressão (regression spec) deveria afirmar que, após um autor excluir uma publicação enfileirada aprovada, o payload do reviewable não contém mais o corpo submetido.

2 curtidas