대기 중인 게시글의 본문이 작성자가 게시글을 삭제한 후에도 검토 대기열에 그대로 남아 있으며 이를 제거할 방법이 없습니다

요약

게시물이 승인 대기열을 거칠 때, ReviewableQueuedPost.payload["raw"]에는 제출된 본문 전체의 사본이 저장됩니다. 게시물이 승인된 후 작성자によって 삭제되면, 공개된 게시물은 “(작성자에 의해 삭제됨)” 스텁으로 대체되지만, 게시글 행이 존재하는 동안(작성자 삭제 후에는 무기한) 리뷰어블(reviewable)은 원래 본문을 유지하며 /review에서 직원에게 계속 제공합니다.

이를 위한 스크럽(scrub) 경로는 없습니다: Reviewable.scrubbable_types는 [ReviewableUser]이므로, #36556에서 추가된 관리자 스크럽 동작(“모더레이터가 이제 ‘스크럽’ 동작을 사용하여 사용자의 개인 데이터를 제거할 수 있습니다”)은 대기열에 있는 게시물을 다루지 않습니다. 페이로드는 게시글 레코드가 영구적으로 파괴될 때만 제거되며, 이는 작성자의 삭제 동작이 수행하지 않는 것이라 UI에서도 토픽의 첫 번째 게시물에 대해 허용되지 않습니다.

v2026.7.1에서 재현되었습니다. 관련 코드는 2026-09-25 기준 main에서 변경되지 않았습니다.

버전

Discourse v2026.7.1 (자체 호스팅, 로컬 인증 테스트 인스턴스). 코어만 — 플러그인은 관여하지 않습니다.

재현 단계

  1. 관리자로, 일반 사용자의 게시물이 승인을 필요하도록 설정합니다. 예를 들어 approve post count를 5로 설정하고 approve unless allowed groups에서 trust_level_0을 제거합니다.

  2. 해당 일반 사용자로, 본문에 고유 마커가 포함된 토픽을 생성합니다. 응답은 {"action": "enqueued"}로 반환됩니다.

  3. 직원으로, 승인합니다: PUT /review/<reviewable_id>/perform/approve_post?version=0. 게시물이 생성됩니다.

  4. 작성자로, 일반 사용자 대상 정상적인 삭제 동작을 통해 삭제합니다 — 해당 게시물이 첫 번째 게시물인 토픽의 경우 DELETE /t/<topic_id>.json입니다.

  5. 직원으로, GET /review.json?status=all을 요청합니다.

기대 동작

작성자가 콘텐츠를 삭제한 후, 동일한 텍스트의 모더레이션 사본은 콘텐츠의 수명 주기를 따라야 합니다: 제거되거나, 스크럽되거나, 적어도 거부된 ReviewableUser처럼 관리자가 스크럽할 수 있어야 합니다.

실제 동작

단계 5에서 여전히 원래 본문 전체가 반환됩니다. 재현 실행에서 측정한 결과:

관찰 항목 결과
작성자 삭제 후 posts 행 user_deleted = t, raw = '(topic deleted by author)'
작성자 삭제 후 reviewables.payload {"raw":"Queued body DRLGQUEUEDfb08ab5b648f. padding …"} — 변경 없음
직원 GET /review.json?status=all 원래 본문 포함
Reviewable.scrubbable_types ["ReviewableUser"]
관리자 PUT /review/<id>/scrub.json HTTP 404 (스크럽 가능한 유형 아님)
게시글 레코드가 영구적으로 파괴된 후 리뷰어블 행이 사라짐 (dependent: :destroy)

마지막 행은 이를 지우는 유일한 경로이며, 작성자의 삭제에서는 도달할 수 없습니다: 작성자의 삭제는 게시글을 user_deleted로 표시하고, delete_removed_posts_after 시간 후 휴지통으로 이동시킵니다. 휴지통에 있는 게시글 행은 계속 존재하므로, 페이로드도 계속 존재합니다.

원인

  • app/models/reviewable_queued_post.rb는 payload['raw']를 생성하고 읽으며, 이를 위한 보존 목적이나 수명 주기에 대한 명시적 규정이 없습니다.

  • lib/post_destroyer.rb:562의 resolve_reviewables_for_author_deletion은 Reviewable.where(target: @post, status: pending)만 다룹니다. 승인된 대기열 게시물 리뷰어블은 절대 상태가 전환되거나 스크럽되지 않습니다.

  • app/models/post.rb:71의 has_many :reviewables, as: :target, dependent: :destroy — 레코드 파괴 시 발생하며, 작성자 삭제가 생성하는 소프트 딜리트 시에는 발생하지 않습니다.

  • app/models/reviewable.rb:81의 scrubbable_types는 [ReviewableUser]를 반환하며, app/controllers/reviewables_controller.rb:191은 추가로 status: rejected를 요구합니다.

  • 리뷰어블을 위한 예약된 정리 작업이 없습니다. app/jobs/scheduled/에는 초안, 이메일 토큰, 내보내기, 업로드, API 키 등에 대한 clean_up_* / purge_* 작업이 있지만, 리뷰어블에 대한 작업은 없습니다.

영향

이것은 공개 유출이 아닙니다 — 리뷰 대기열은 직원 전용이며, 저는 그렇지 않다고 주장하지 않습니다. 이는 데이터 보존 갭입니다: 사용자가 포럼에서 제거한 텍스트가 무기한으로 모더레이션 저장소에 남아있으며, 리뷰 대기열 UI에 표시되고, 게시글 레코드를 영구적으로 파괴하는 것을 제외하고는 이를 제거할 수 있는 관리자 동작이 없습니다.

이는 #36556이 거부된 사용자를 위한 스크럽을 추가한 것과 같은 이유로 중요합니다: 리뷰어블 페이로드는 개인 데이터를 포함할 수 있으며, 삭제 요청을 받는 사이트는 대기열 게시물 페이로드를 지울 수 있는 지원되는 방법이 없습니다.

제안된 수정

다음 중 어느 하나만으로도 해결되며, 둘 다 수행하는 것이 더 좋습니다:

  1. resolve_reviewables_for_author_deletion(및 휴지통 경로)를 확장하여, 대상 게시물이 작성자에 의해 삭제되었거나 휴지통에 이동된 리뷰어블의 payload['raw']를 스크럽합니다.

  2. ReviewableQueuedPost를 Reviewable.scrubbable_types에 추가하고, 대상 게시물이 더 이상 콘텐츠를 포함하지 않을 때 기존 관리자 스크럽 동작이 적용되도록 하여, 수정 전에 생성된 페이로드에 대해 운영자가 지원되는 구제 수단을 가질 수 있도록 합니다.

회귀 스펙은 작성자가 승인된 대기열 게시물을 삭제한 후, 리뷰어블 페이로드에 제출된 본문이 더 이상 포함되지 않는다는 것을 주장해야 합니다.

2개의 좋아요