From df5e756862a52372a48d60652f594186a4704d71 Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 17 Jun 2026 00:00:00 +0000 Subject: [PATCH 1/2] Do not notify users unable to access post Known implementations all intend to only tag users who are _also_ allowed to access the post, but this is not guaranteed by ActivityPub semantics, thus recheck against the authorative AP addressing fields. An alternative worth considering is directly using the addressing fields to decide whom to notify ignoring tags. However, MastoAPI later uses tags and thus this may lead to confusing results if a recipient is addressed but not tagged and receives a notification about being mentioned while the API claims they are not mentioned. Addresses the receiving side of: https://akkoma.dev/AkkomaGang/akkoma/issues/1135 (cherry picked from commit 5cff1fac24bb3e6c22160202a7b846f8a62f2477) --- lib/pleroma/web/common_api/utils.ex | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 255803c14..64958e1f2 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -435,13 +435,54 @@ defmodule Pleroma.Web.CommonAPI.Utils do %{} end - tagged_mentions = maybe_extract_mentions(object_data) + tagged_mentions = + maybe_extract_mentions(object_data) + |> filter_tags_to_qualified(activity) recipients ++ tagged_mentions end def maybe_notify_mentioned_recipients(recipients, _), do: recipients + # Filter tagged mentions to ensure they can actually access the status + defp filter_tags_to_qualified(tagged_mentions, %Activity{ + recipients: recipients, + data: data, + actor: actor + }) do + recipients = + if recipients && recipients != [], + do: recipients, + else: + (data["to"] || []) ++ (data["cc"] || []) ++ (data["bto"] || []) ++ (data["bcc"] || []) + + cond do + recipients == [] -> + [] + + Pleroma.Constants.as_public() in recipients -> + tagged_mentions + + true -> + author = User.get_cached_by_ap_id(actor) + to_followers = author.follower_address in recipients + + Enum.filter(tagged_mentions, fn tagged -> + cond do + tagged in recipients -> + true + + to_followers -> + tagged_user = User.get_cached_by_ap_id(tagged) + User.following?(tagged_user, author) + + true -> + false + end + end) + end + end + def maybe_notify_followers(recipients, %Activity{data: %{"type" => "Move"}} = activity) do with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do user From cccbb39309104263fc7ebaf4b7c196ab3d9a9ac5 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 27 Oct 2026 12:31:18 +0400 Subject: [PATCH 2/2] fix(notifications): handle unknown users when filtering tagged mentions The access check from the previous commit crashed on tags of unknown users in followers-only posts, so such posts failed to be received. Also add tests for the check. --- changelog.d/mention-notify-access.security | 1 + lib/pleroma/web/common_api/utils.ex | 8 +- test/pleroma/notification_test.exs | 98 ++++++++++++++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 changelog.d/mention-notify-access.security diff --git a/changelog.d/mention-notify-access.security b/changelog.d/mention-notify-access.security new file mode 100644 index 000000000..2f1830079 --- /dev/null +++ b/changelog.d/mention-notify-access.security @@ -0,0 +1 @@ +Do not notify tagged users who cannot see the post diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 64958e1f2..b66e0ace6 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -465,7 +465,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do true -> author = User.get_cached_by_ap_id(actor) - to_followers = author.follower_address in recipients + to_followers = match?(%User{}, author) and author.follower_address in recipients Enum.filter(tagged_mentions, fn tagged -> cond do @@ -473,8 +473,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do true to_followers -> - tagged_user = User.get_cached_by_ap_id(tagged) - User.following?(tagged_user, author) + case User.get_cached_by_ap_id(tagged) do + %User{} = tagged_user -> User.following?(tagged_user, author) + _ -> false + end true -> false diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index 992b15f8b..694af27aa 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -7,6 +7,8 @@ defmodule Pleroma.NotificationTest do import Pleroma.Factory + require Pleroma.Constants + alias Pleroma.FollowingRelationship alias Pleroma.Notification alias Pleroma.Repo @@ -611,6 +613,102 @@ defmodule Pleroma.NotificationTest do assert other_user in enabled_receivers end + defp incoming_note_tagging(author, to, tagged_ap_ids) do + tags = Enum.map(tagged_ap_ids, &%{"type" => "Mention", "href" => &1, "name" => &1}) + + {:ok, activity} = + Transmogrifier.handle_incoming(%{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Create", + "to" => to, + "actor" => author.ap_id, + "object" => %{ + "type" => "Note", + "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(), + "to" => to, + "content" => "tagged", + "tag" => tags, + "attributedTo" => author.ap_id + } + }) + + activity + end + + defp notified_ap_ids(activity), + do: activity |> Notification.get_notified_from_activity() |> Enum.map(& &1.ap_id) + + test "it does not notify tagged users who cannot see a followers-only post" do + author = insert(:user) + tagged = insert(:user) + + activity = incoming_note_tagging(author, [author.follower_address], [tagged.ap_id]) + + refute tagged.ap_id in notified_ap_ids(activity) + end + + test "it notifies tagged followers of a followers-only post" do + author = insert(:user) + tagged = insert(:user) + {:ok, _, _} = Pleroma.User.follow(tagged, author) + + activity = incoming_note_tagging(author, [author.follower_address], [tagged.ap_id]) + + assert tagged.ap_id in notified_ap_ids(activity) + end + + test "it does not notify tagged users who are not addressed by a direct message" do + author = insert(:user) + addressed = insert(:user) + tagged = insert(:user) + + activity = incoming_note_tagging(author, [addressed.ap_id], [tagged.ap_id]) + receivers = notified_ap_ids(activity) + + assert addressed.ap_id in receivers + refute tagged.ap_id in receivers + end + + test "it notifies tagged users of a public post even if they are not addressed" do + author = insert(:user) + tagged = insert(:user) + + activity = + incoming_note_tagging(author, [Pleroma.Constants.as_public()], [tagged.ap_id]) + + assert tagged.ap_id in notified_ap_ids(activity) + end + + test "it does not notify tagged users with a pending follow request" do + author = insert(:user, is_locked: true) + tagged = insert(:user) + {:ok, _, _, _} = CommonAPI.follow(author, tagged) + + activity = incoming_note_tagging(author, [author.follower_address], [tagged.ap_id]) + + refute tagged.ap_id in notified_ap_ids(activity) + end + + test "it ignores tags of unknown users in a followers-only post" do + author = insert(:user) + tagged = insert(:user) + + follower = insert(:user) + {:ok, _, _} = Pleroma.User.follow(follower, author) + + activity = + incoming_note_tagging(author, [author.follower_address], [ + "https://unknown.example/users/nobody", + tagged.ap_id, + follower.ap_id + ]) + + receivers = notified_ap_ids(activity) + + refute tagged.ap_id in receivers + assert follower.ap_id in receivers + end + test "it does not send notifications to users who are only cc in new messages" do user = insert(:user) other_user = insert(:user)