From ec4c897fe431844e082526d98cf53177e316199d Mon Sep 17 00:00:00 2001 From: omerfaruk-pseud Date: Fri, 23 Jan 2026 01:54:33 +0300 Subject: [PATCH 1/5] AO3-7271 take cf data from header to model then akismet --- app/controllers/comments_controller.rb | 3 +++ app/models/comment.rb | 5 +++++ spec/models/comment_spec.rb | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index a9be99d451..470ac479df 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -391,6 +391,9 @@ def create @comment = Comment.new(comment_params) @comment.ip_address = request.remote_ip @comment.user_agent = request.env["HTTP_USER_AGENT"]&.to(499) + @comment.cloudflare_bot_score = headers['cf-bot-score'] + @comment.cloudflare_ja3_hash = headers['cf-ja3-hash'] + @comment.cloudflare_ja4 = headers['cf-ja4'] @comment.commentable = Comment.commentable_object(@commentable) @controller_name = params[:controller_name] diff --git a/app/models/comment.rb b/app/models/comment.rb index e9effa2ac9..95799d4b20 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -24,6 +24,8 @@ class Comment < ApplicationRecord delegate :user, to: :pseud, allow_nil: true + attr_accessor :cloudflare_bot_score, :cloudflare_ja3_hash, :cloudflare_ja4 + # Whether the writer of the comment this is replying to allows guest replies validate :guest_can_reply, if: :reply_comment?, unless: :pseud_id, on: :create def guest_can_reply @@ -131,6 +133,9 @@ def akismet_attributes user_ip: ip_address, user_agent: user_agent, user_role: user_role, + cloudflare_bot_score: cloudflare_bot_score, + cloudflare_ja3_hash: cloudflare_ja3_hash, + cloudflare_ja4: cloudflare_ja4, comment_author: comment_author, comment_author_email: comment_owner_email, comment_content: comment_content, diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index 7136e2ca6d..38608c9c74 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -251,6 +251,10 @@ def queue_adapter_for_test expect(subject.akismet_attributes[:comment_author_email]).to eq(subject.pseud.user.email) end + it "has cloudflare bot score" do + expect(subject.akismet_attributes[:cloudflare_bot_score]).to eq(subject.cloudflare_bot_score) + end + context "when the comment is being created" do let(:new_comment) do Comment.new(commentable: subject, From d13eb559fa3f9e54a7d07e907fb448c7353b4e4f Mon Sep 17 00:00:00 2001 From: omerfaruk-pseud Date: Fri, 30 Jan 2026 12:22:47 +0300 Subject: [PATCH 2/5] AO3-7271 add cf data only when it exists --- app/models/comment.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/comment.rb b/app/models/comment.rb index 95799d4b20..3da6c01f61 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -133,9 +133,6 @@ def akismet_attributes user_ip: ip_address, user_agent: user_agent, user_role: user_role, - cloudflare_bot_score: cloudflare_bot_score, - cloudflare_ja3_hash: cloudflare_ja3_hash, - cloudflare_ja4: cloudflare_ja4, comment_author: comment_author, comment_author_email: comment_owner_email, comment_content: comment_content, @@ -143,6 +140,10 @@ def akismet_attributes comment_post_modified_gmt: comment_post_modified_gmt } + attributes[:cloudflare_bot_score] = cloudflare_bot_score if cloudflare_bot_score + attributes[:cloudflare_ja3_hash] = cloudflare_ja3_hash if cloudflare_ja3_hash + attributes[:cloudflare_ja4] = cloudflare_ja4 if cloudflare_ja4 + attributes[:recheck_reason] = "edit" if will_save_change_to_edited_at? && will_save_change_to_comment_content? attributes From 21b95b58e8cdd00f38d17cde81e6d86ef9f0ac5b Mon Sep 17 00:00:00 2001 From: omerfaruk-pseud Date: Fri, 30 Jan 2026 12:51:43 +0300 Subject: [PATCH 3/5] AO3-7271 use double quotes, rubocop --- app/controllers/comments_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 470ac479df..e1927ff0fb 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -391,9 +391,9 @@ def create @comment = Comment.new(comment_params) @comment.ip_address = request.remote_ip @comment.user_agent = request.env["HTTP_USER_AGENT"]&.to(499) - @comment.cloudflare_bot_score = headers['cf-bot-score'] - @comment.cloudflare_ja3_hash = headers['cf-ja3-hash'] - @comment.cloudflare_ja4 = headers['cf-ja4'] + @comment.cloudflare_bot_score = headers["cf-bot-score"] + @comment.cloudflare_ja3_hash = headers["cf-ja3-hash"] + @comment.cloudflare_ja4 = headers["cf-ja4"] @comment.commentable = Comment.commentable_object(@commentable) @controller_name = params[:controller_name] From b5c9815af7dcf3648c228b97ac76a0ac27d388d8 Mon Sep 17 00:00:00 2001 From: omerfaruk-pseud Date: Mon, 2 Feb 2026 19:19:37 +0300 Subject: [PATCH 4/5] AO3-7271 add tests --- app/controllers/comments_controller.rb | 6 +-- .../comments/default_rails_actions_spec.rb | 27 ++++++++++++ spec/models/comment_spec.rb | 44 +++++++++++++++++-- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index e1927ff0fb..6e3bdb8b4b 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -391,9 +391,9 @@ def create @comment = Comment.new(comment_params) @comment.ip_address = request.remote_ip @comment.user_agent = request.env["HTTP_USER_AGENT"]&.to(499) - @comment.cloudflare_bot_score = headers["cf-bot-score"] - @comment.cloudflare_ja3_hash = headers["cf-ja3-hash"] - @comment.cloudflare_ja4 = headers["cf-ja4"] + @comment.cloudflare_bot_score = request.env["HTTP_CF_BOT_SCORE"] + @comment.cloudflare_ja3_hash = request.env["HTTP_CF_JA3_HASH"] + @comment.cloudflare_ja4 = request.env["HTTP_CF_JA4"] @comment.commentable = Comment.commentable_object(@commentable) @controller_name = params[:controller_name] diff --git a/spec/controllers/comments/default_rails_actions_spec.rb b/spec/controllers/comments/default_rails_actions_spec.rb index 7faaa25799..4057787c09 100644 --- a/spec/controllers/comments/default_rails_actions_spec.rb +++ b/spec/controllers/comments/default_rails_actions_spec.rb @@ -661,6 +661,33 @@ end end end + + context "when cloudflare headers are available" do + let!(:comment) { create(:comment) } + before { fake_login } + + it "sets the bot score" do + request.env["HTTP_CF_BOT_SCORE"] = "42" + expect_any_instance_of(Comment).to receive(:cloudflare_bot_score=).with("42") + + post :create, params: { comment_id: comment.id, comment: anon_comment_attributes} + end + + it "sets the ja3 hash" do + request.env["HTTP_CF_JA3_HASH"] = "a_hash" + expect_any_instance_of(Comment).to receive(:cloudflare_ja3_hash=).with("a_hash") + + post :create, params: { comment_id: comment.id, comment: anon_comment_attributes} + end + + it "sets the ja3 hash" do + request.env["HTTP_CF_JA4"] = "another_hash" + expect_any_instance_of(Comment).to receive(:cloudflare_ja4=).with("another_hash") + + post :create, params: { comment_id: comment.id, comment: anon_comment_attributes} + end + end + end describe "DELETE #destroy" do diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index 38608c9c74..c6e29bcfa0 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -251,10 +251,6 @@ def queue_adapter_for_test expect(subject.akismet_attributes[:comment_author_email]).to eq(subject.pseud.user.email) end - it "has cloudflare bot score" do - expect(subject.akismet_attributes[:cloudflare_bot_score]).to eq(subject.cloudflare_bot_score) - end - context "when the comment is being created" do let(:new_comment) do Comment.new(commentable: subject, @@ -359,6 +355,46 @@ def queue_adapter_for_test end end end + + context "when cloudflare headers are available" do + before do + subject.cloudflare_bot_score = "42" + subject.cloudflare_ja3_hash = "a_hash" + subject.cloudflare_ja4 = "another_hash" + end + + it "has cloudflare bot score" do + expect(subject.akismet_attributes[:cloudflare_bot_score]).to eq("42") + end + + it "has cloudflare ja3 hash" do + expect(subject.akismet_attributes[:cloudflare_ja3_hash]).to eq("a_hash") + end + + it "has cloudflare ja4" do + expect(subject.akismet_attributes[:cloudflare_ja4]).to eq("another_hash") + end + end + + context "when cloudflare headers aren't available" do + before do + subject.cloudflare_bot_score = nil + subject.cloudflare_ja3_hash = nil + subject.cloudflare_ja4 = nil + end + + it "doesn't have cloudflare bot score" do + expect(subject.akismet_attributes).not_to have_key(:cloudflare_bot_score) + end + + it "doesn't have cloudflare ja3 hash" do + expect(subject.akismet_attributes).not_to have_key(:cloudflare_ja3_hash) + end + + it "doesn't have cloudflare ja4" do + expect(subject.akismet_attributes).not_to have_key(:cloudflare_ja4) + end + end end end From 58b90c1f6d2840ba6e83de325e8f09c5e9e05314 Mon Sep 17 00:00:00 2001 From: omerfaruk-pseud Date: Mon, 2 Feb 2026 19:40:35 +0300 Subject: [PATCH 5/5] AO3-7271 add and remove some whitespaces --- spec/controllers/comments/default_rails_actions_spec.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/controllers/comments/default_rails_actions_spec.rb b/spec/controllers/comments/default_rails_actions_spec.rb index 4057787c09..35d57d398c 100644 --- a/spec/controllers/comments/default_rails_actions_spec.rb +++ b/spec/controllers/comments/default_rails_actions_spec.rb @@ -670,24 +670,23 @@ request.env["HTTP_CF_BOT_SCORE"] = "42" expect_any_instance_of(Comment).to receive(:cloudflare_bot_score=).with("42") - post :create, params: { comment_id: comment.id, comment: anon_comment_attributes} + post :create, params: { comment_id: comment.id, comment: anon_comment_attributes } end it "sets the ja3 hash" do request.env["HTTP_CF_JA3_HASH"] = "a_hash" expect_any_instance_of(Comment).to receive(:cloudflare_ja3_hash=).with("a_hash") - post :create, params: { comment_id: comment.id, comment: anon_comment_attributes} + post :create, params: { comment_id: comment.id, comment: anon_comment_attributes } end it "sets the ja3 hash" do request.env["HTTP_CF_JA4"] = "another_hash" expect_any_instance_of(Comment).to receive(:cloudflare_ja4=).with("another_hash") - post :create, params: { comment_id: comment.id, comment: anon_comment_attributes} + post :create, params: { comment_id: comment.id, comment: anon_comment_attributes } end end - end describe "DELETE #destroy" do