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
-
Como administrador, configure para que as publicações de um usuário comum exijam aprovação, por exemplo, defina
approve post countpara 5 e removatrust_level_0deapprove unless allowed groups. -
Como esse usuário comum, crie um tópico com um marcador único no corpo. A resposta retorna como
{"action": "enqueued"}. -
Como membro da equipe, aprove-o:
PUT /review/<reviewable_id>/perform/approve_post?version=0. A publicação é criada. -
Como o autor, exclua-a através da ação normal voltada para o usuário —
DELETE /t/<topic_id>.jsonpara o tópico cuja primeira publicação esta seja. -
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.rbconstrói e lêpayload['raw']; nada declara um propósito ou tempo de retenção para ele. -
lib/post_destroyer.rb:562resolve_reviewables_for_author_deletionapenas toca emReviewable.where(target: @post, status: pending). Um reviewable de publicação enfileirada aprovada nunca é transicionado ou limpo. -
app/models/post.rb:71has_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:81scrubbable_typesretorna[ReviewableUser];app/controllers/reviewables_controller.rb:191adicionalmente exigestatus: rejected. -
Não há limpeza agendada para reviewables.
app/jobs/scheduled/contém jobsclean_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:
-
Estender
resolve_reviewables_for_author_deletion(e o caminho de lixeira) para limparpayload['raw']em reviewables cuja publicação alvo tenha sido excluída pelo autor ou movida para a lixeira. -
Adicionar
ReviewableQueuedPostaReviewable.scrubbable_typese 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.