fix(federation): do not deliver to instances rejected by SimplePolicy

Public activities went to every inbox, so followers on an instance in
the SimplePolicy reject list still got our posts. With authorized fetch
mode on, signed fetches from those instances were also accepted, unless
the instance was in :instance, :rejected_instances too.

Check the SimplePolicy reject and accept lists for deliveries and signed
fetches, when the policy is enabled. Quarantine keeps its meaning (no
private activities). An inbox without a host no longer crashes.

Based on the Akkoma changes 90c4785b8, ca000f830, 03662501c and d2b0d8647.

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Co-authored-by: Atsuko Karagi <atsuko.karagi@localhost>
This commit is contained in:
Lain Soykaf 2026-10-27 12:33:35 +04:00
commit a766335679
No known key found for this signature in database
9 changed files with 177 additions and 18 deletions

View file

@ -164,6 +164,39 @@ defmodule Pleroma.Web.ActivityPub.MRF do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end
@doc """
Returns a function that is false for a host if SimplePolicy is enabled and
rejects it, or has an accept list without it. Used for deliveries and signed
fetches, not only for incoming activities. Build it once and reuse it: it
compiles the instance lists.
"""
@spec instance_filter() :: (String.t() | nil -> boolean())
def instance_filter do
if Pleroma.Web.ActivityPub.MRF.SimplePolicy in get_policies() do
reject = simple_policy_domains(:reject)
accept = simple_policy_domains(:accept)
fn
host when is_binary(host) ->
not subdomain_match?(reject, host) and (accept == [] or subdomain_match?(accept, host))
_ ->
false
end
else
&is_binary/1
end
end
@spec instance_allowed?(String.t() | nil) :: boolean()
def instance_allowed?(host), do: instance_filter().(host)
defp simple_policy_domains(key) do
Pleroma.Config.get([:mrf_simple, key], [])
|> instance_list_from_tuples()
|> subdomains_regex()
end
@spec instance_list_from_tuples([{String.t(), String.t()}]) :: [String.t()]
def instance_list_from_tuples(list) do
Enum.map(list, fn {instance, _} -> instance end)

View file

@ -333,12 +333,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
%{
key: :reject,
description:
"List of instances to reject activities from (except deletes) and the reason for doing so"
"List of instances to reject activities from (except deletes) and the reason for doing so. We also do not deliver to them, and refuse their signed fetches in authorized fetch mode"
},
%{
key: :accept,
description:
"List of instances to only accept activities from (except deletes) and the reason for doing so"
"List of instances to only accept activities from (except deletes) and the reason for doing so. If set, we also only deliver to them, and refuse signed fetches from others in authorized fetch mode"
},
%{
key: :followers_only,

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.ActivityPub.Publisher.Prepared
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Workers.PublisherWorker
@ -221,18 +222,25 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
end
end
def should_federate?(nil, _), do: false
def should_federate?(_, true), do: true
def should_federate?(inbox, public), do: federation_filter(public).(inbox)
def should_federate?(inbox, _) do
%{host: host} = URI.parse(inbox)
# Compiles the instance lists once, for filtering many inboxes.
defp federation_filter(public) do
allowed? = MRF.instance_filter()
quarantined_instances =
quarantined =
Config.get([:instance, :quarantined_instances], [])
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
|> MRF.instance_list_from_tuples()
|> MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
fn
nil ->
false
inbox ->
%{host: host} = URI.parse(inbox)
allowed?.(host) and (public or not MRF.subdomain_match?(quarantined, host))
end
end
@spec recipients(User.t(), Activity.t()) :: [[User.t()]]
@ -312,7 +320,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity)
when is_list(bcc) and bcc != [] do
public = public?(activity)
should_federate? = activity |> public?() |> federation_filter()
[priority_recipients, recipients] = recipients(actor, activity)
@ -324,7 +332,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
determine_inbox(activity, user)
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Enum.filter(should_federate?)
|> Instances.filter_reachable()
end)
@ -360,6 +368,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
Relay.publish(activity)
end
should_federate? = federation_filter(public)
[priority_inboxes, inboxes] =
recipients(actor, activity)
|> Enum.map(fn recipients ->
@ -368,7 +378,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
determine_inbox(activity, user)
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Enum.filter(should_federate?)
end)
inboxes = inboxes -- priority_inboxes

View file

@ -101,7 +101,7 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
conn.assigns[:actor_id] do
%{host: host} = URI.parse(conn.assigns.actor_id)
if MRF.subdomain_match?(rejected_domains(), host) do
if MRF.subdomain_match?(rejected_domains(), host) or not MRF.instance_allowed?(host) do
conn
|> put_status(:unauthorized)
|> halt()