PR #926 리뷰에서 나온 항목. #926 이 도입한 Post.find_by(id:) 가드의 부작용이다.
문제
reply_target_attributes 의 로컬 /posts/N 분기에 존재 가드가 붙으면서, 대상이 없을 때 조용히 다음 분기로 흘러 최종적으로 {} 를 반환한다. 그 결과 저장이 거부되던 것이 최상위 포스트 저장으로 바뀌었다.
app/models/post.rb:49 → :241-247 에 검증이 있다:
def validate_parent_post
return unless parent_id.present?
errors.add(:parent_id, "원본 포스트를 찾을 수 없습니다.") if parent.nil?
end
inReplyTo = https://<로컬>/posts/<없는id> |
결과 |
| #926 이전 |
parent_id 설정 → validate_parent_post 실패 → RecordInvalid → 저장 안 됨 |
| #926 이후 |
{} → parent/article 없는 최상위 :short 포스트로 저장 |
handle_federated_object? 는 URL 에 /posts/ 문자열이 들어있는지만 본다:
if reply_target_host_kind(in_reply_to) == :local
return true if in_reply_to.include?("/posts/") || in_reply_to.include?("/articles/")
end
따라서 원격 액터가 존재하지 않는 로컬 post URL 을 inReplyTo 에 넣으면 인박스를 통과하고, 우리 타임라인에 최상위 글로 저장된다. 이전에는 검증이 이를 막았다.
참고로 이슈 #871 이 전제한 "dangling parent_id 는 FK 위반" 도 부정확하다. FK 이전에 검증이 먼저 잡는다.
관측 가능성 문제
{} 로 떨어질 때 남는 warn 이 두 가지 다른 사건을 한 문장으로 뭉갠다:
- 정말 모르는 원격 URL — 일상적, 무해
- 로컬
/posts/N 패턴에 매치됐는데 그 post 가 없음 — 데이터 무결성 신호 또는 공격
운영자가 로그만 보고 둘을 분리할 수 없다.
제안
handle_federated_object? 가 로컬 URL 을 수락하기 전에 대상의 실제 존재를 확인한다. 지금은 "URL 에 문자열이 들어있는가"만 본다.
if reply_target_host_kind(in_reply_to) == :local
return true if (id = in_reply_to[%r{/posts/(\d+)}, 1]) && Post.exists?(id: id)
return true if (id = in_reply_to[%r{/articles/(\d+)}, 1]) && Article.exists?(id: id)
end
그리고 reply_target_attributes 의 폴백에서 "로컬 패턴 매치 + 대상 없음" 을 별도 메시지로 로깅해 위 두 사건을 분리한다.
주의: 이건 인박스 수용 정책 변경이다. 정상 경로(로컬 post 에 대한 원격 답글)는 발행된 inReplyTo 가 /federation/published/posts/<숫자id> 형태라 숫자 정규식에 매칭되므로 영향받지 않는다(실측 확인). 다만 슬러그 URL(/posts/<slug>, should_federate? 가 false 라 federated_url 이 nil 인 경우의 폴백)은 정규식에 안 걸리므로, 위 변경을 그대로 넣으면 그 경로가 거부된다. 슬러그 해석을 함께 넣을지 결정 필요.
테스트
test/models/concerns/posts/federation_ingest_test.rb 의 "local /posts/ URL for a missing post yields no parent_id" 는 현재 이 동작을 핀 고정할 뿐 타당성은 묻지 않는다. 수용 정책을 정한 뒤 이 테스트의 기대를 함께 갱신해야 한다.
관련: #871, #926
PR #926 리뷰에서 나온 항목. #926 이 도입한
Post.find_by(id:)가드의 부작용이다.문제
reply_target_attributes의 로컬/posts/N분기에 존재 가드가 붙으면서, 대상이 없을 때 조용히 다음 분기로 흘러 최종적으로{}를 반환한다. 그 결과 저장이 거부되던 것이 최상위 포스트 저장으로 바뀌었다.app/models/post.rb:49→:241-247에 검증이 있다:inReplyTo = https://<로컬>/posts/<없는id>parent_id설정 →validate_parent_post실패 →RecordInvalid→ 저장 안 됨{}→ parent/article 없는 최상위:short포스트로 저장handle_federated_object?는 URL 에/posts/문자열이 들어있는지만 본다:따라서 원격 액터가 존재하지 않는 로컬 post URL 을
inReplyTo에 넣으면 인박스를 통과하고, 우리 타임라인에 최상위 글로 저장된다. 이전에는 검증이 이를 막았다.참고로 이슈 #871 이 전제한 "dangling parent_id 는 FK 위반" 도 부정확하다. FK 이전에 검증이 먼저 잡는다.
관측 가능성 문제
{}로 떨어질 때 남는 warn 이 두 가지 다른 사건을 한 문장으로 뭉갠다:/posts/N패턴에 매치됐는데 그 post 가 없음 — 데이터 무결성 신호 또는 공격운영자가 로그만 보고 둘을 분리할 수 없다.
제안
handle_federated_object?가 로컬 URL 을 수락하기 전에 대상의 실제 존재를 확인한다. 지금은 "URL 에 문자열이 들어있는가"만 본다.그리고
reply_target_attributes의 폴백에서 "로컬 패턴 매치 + 대상 없음" 을 별도 메시지로 로깅해 위 두 사건을 분리한다.주의: 이건 인박스 수용 정책 변경이다. 정상 경로(로컬 post 에 대한 원격 답글)는 발행된
inReplyTo가/federation/published/posts/<숫자id>형태라 숫자 정규식에 매칭되므로 영향받지 않는다(실측 확인). 다만 슬러그 URL(/posts/<slug>,should_federate?가 false 라federated_url이 nil 인 경우의 폴백)은 정규식에 안 걸리므로, 위 변경을 그대로 넣으면 그 경로가 거부된다. 슬러그 해석을 함께 넣을지 결정 필요.테스트
test/models/concerns/posts/federation_ingest_test.rb의"local /posts/ URL for a missing post yields no parent_id"는 현재 이 동작을 핀 고정할 뿐 타당성은 묻지 않는다. 수용 정책을 정한 뒤 이 테스트의 기대를 함께 갱신해야 한다.관련: #871, #926