logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
commit: f8db412af42dad0eb5f3510030fc089dba77d822
parent 59fcb5c96ebebad6973f294d47ec6f7ed75b38bd
Author: Oneric <oneric@oneric.stub>
Date:   Sun, 23 Nov 2025 00:00:00 +0000

fed/fetch: don't serve unsanitised object data for some activities

When the object associated with the activity was preloaded
(which happens automatically with Activity.normalize used in the
 controller) Object.normalize’s "id_only" option did not actually work.
This option and it’s usage were introduced to fix display of Undo
activities in e88f36f72b5317debafcc4209b91eb35ad8f0691.
For "Undo"s (and "Delete"s) there is no object preloaded
(since it is already gone from the database) thus this appeared
to work and for the particular case considered there in fact did.
Create activities use different rendering logic and thus remained
unaffected too.

However, for all other types of Activities (yes, including Update
which really _should_ include a properly sanitised, full object)
this new attempt at including "just the id", lead to it instead
including the full, unsanitised data of the referenced object.

This is obviously bad and can get worse due to access restrictions
on the activity being solely performed based on the addressing
of the activity itself, not of the (unintentionally) embedded
object.

Starting with the obvious, this leaks all "internal" fields
but as already mentioned in 8243fc0ef482a28daf2bcae2c64a9510bdb76489
all current "internal" fields from Constants.object_internal_fields
are already publicised via MastoAPI etc anyway. Assuming matching
addressing of the referenced object and activity this isn't problematic
with regard to confidentiality.
Except, the internal "voters" field recording who voted for a poll
is currently just omitted from Constants.object_internal_fields
and indeed confidential information (fix in subsequent commit).
Fortunately this list is for the poll as a whole and there are no
inlined lists for individual choices. While this thus leaks _who_
voted for a poll, it at least doesn't directly expose _what_ each voter
chose if there are multiple voters.

As alluded to before, the access restriction not being aware
of the misplaced object data into account makes the issue worse.
If the activity addressing is not a subset of the referenced object’s
addressing, this will leak private objects to unauthorised users.
This begs the question whether such mismatched addressing can occur.
For remote activities the answer is ofc a resounding YES,
but we only serve local ActivityPub objects and for the latter
it currently(!) seems like a "no".
For all intended interactions, the user interacting must already have
access to the object of interest and our ActivityPub Builder
already uses a subset of the original posts addressing for
posts not publicly accessible. This addressing creation logic
was last touched six years ago predating the introduction of this
exposure blunder.
The rather big caveat her being, until it was fixed just yesterday in
dff532ac723310903e58c5d28f897cc2d116594f it was indeed possible to
interact with posts one is not allowed to actually see. Combined, this
allowed unauthorised access to private posts. (The API ID of such
private posts can be obtained e.g. from replies one _is_ allowed to see)

During the time when ActivityPub C2S was supported there might have been
more ways to create activities with mismatched addressing and sneak a
peek on private posts. (The AP id can be obtained in an analogous way)

Replaces and fixes e88f36f72b5317debafcc4209b91eb35ad8f0691.
Since there never were any users of the
bugged "id_only" option it is removed.

This was reported by silverpill <silverpill@firemail.cc> as an
ActivityPub interop issue, since this blunder of course also
leads to invalid AP documents by adding an additional layer
in form of the "data" key and directly exposing the internal
Pleroma representation which is not always identical to valid AP.

Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/1017

Diffstat:

Mlib/pleroma/object.ex5+----
Mlib/pleroma/web/activity_pub/views/object_view.ex6+++++-
2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex @@ -126,7 +126,7 @@ defmodule Pleroma.Object do Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") end - def normalize(_, options \\ [fetch: false, id_only: false]) + def normalize(_, options \\ [fetch: false]) # If we pass an Activity to Object.normalize(), we can try to use the preloaded object. # Use this whenever possible, especially when walking graphs in an O(N) loop! @@ -155,9 +155,6 @@ defmodule Pleroma.Object do def normalize(ap_id, options) when is_binary(ap_id) do cond do - Keyword.get(options, :id_only) -> - ap_id - Keyword.get(options, :fetch) -> case Fetcher.fetch_object_from_id(ap_id, options) do {:ok, object} -> object diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do alias Pleroma.Activity alias Pleroma.Object alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.Utils def render("object.json", %{object: %Object{} = object}) do base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(object.data) @@ -29,7 +30,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do def render("object.json", %{object: %Activity{} = activity}) do base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header(activity.data) - object_id = Object.normalize(activity, id_only: true) + object_id = object_id_from_activity(activity) additional = Transmogrifier.prepare_object(activity.data) @@ -37,4 +38,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do Map.merge(base, additional) end + + defp object_id_from_activity(%Activity{object: %Object{data: %{"id" => obj_id}}}), do: obj_id + defp object_id_from_activity(%Activity{data: %{"object" => ap_object_ref}}), do: Utils.get_ap_id(ap_object_ref) end