logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
commit: b3887a6fa775bda1f51efab4774cba838ad0db91
parent 75353282ee140e535e551b02e6f54d3f8a8666ca
Author: Phantasm <phantasm@centrum.cz>
Date:   Tue,  2 Dec 2025 23:25:42 +0100

AP C2S: Validate visibility for C2S requests to /users/:nickname/outbox

A local user could previously send Announce/EmojiReact/Like activities
to their outbox referencing objects that aren't visible to them and they
would get processed as if can see them. Only requirement is knowing
the URI of the object and the users instance having C2S enabled (currently
disabled by default).

Diffstat:

Mlib/pleroma/web/activity_pub/activity_pub_controller.ex21++++++++++++++++++++-
Mtest/pleroma/web/activity_pub/activity_pub_controller_test.exs76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -482,6 +482,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do {:ok, activity} end + defp validate_visibility(%User{} = user, %{"type" => type, "object" => object} = activity) do + with {_, %Object{} = normalized_object} <- {:normalize, Object.normalize(object, fetch: false)}, + {_, true} <- {:visibility, Visibility.visible_for_user?(normalized_object, user)} do + {:ok, activity} + else + {:normalize, _} -> + if user.local and type == "Create" do + # Creating new object via C2S + {:ok, activity} + else + {:error, "No such object found"} + end + + {:visibility, _} -> + {:forbidden, "You can't interact with this object"} + end + end + def update_outbox( %{assigns: %{user: %User{nickname: nickname, ap_id: actor} = user}} = conn, %{"nickname" => nickname} = params @@ -493,7 +511,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do |> Map.put("actor", actor) with {:ok, params} <- fix_user_message(user, params), - {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true), + {:ok, activity} <- validate_visibility(user, params), + {:ok, activity, _} <- Pipeline.common_pipeline(activity, local: true), %Activity{data: activity_data} <- Activity.normalize(activity) do conn |> put_status(:created) diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -1706,6 +1706,82 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert note_object == Object.normalize(note_activity, fetch: false) end + test "it rejects like activity to object invisible to actor", %{conn: conn} do + user = insert(:user) + stranger = insert(:user, local: true) + {:ok, post} = CommonAPI.post(user, %{status: "cofe", visibility: "private"}) + + assert Pleroma.Web.ActivityPub.Visibility.private?(post) + + post_object = Object.normalize(post, fetch: false) + + data = %{ + type: "Like", + object: %{ + id: post_object.data["id"] + } + } + + conn = + conn + |> assign(:user, stranger) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{stranger.nickname}/outbox", data) + + assert json_response(conn, 403) + end + + test "it rejects announce activity to object invisible to actor", %{conn: conn} do + user = insert(:user) + stranger = insert(:user, local: true) + {:ok, post} = CommonAPI.post(user, %{status: "cofe", visibility: "private"}) + + assert Pleroma.Web.ActivityPub.Visibility.private?(post) + + post_object = Object.normalize(post, fetch: false) + + data = %{ + type: "Announce", + object: %{ + id: post_object.data["id"] + } + } + + conn = + conn + |> assign(:user, stranger) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{stranger.nickname}/outbox", data) + + assert json_response(conn, 403) + end + + test "it rejects emojireact activity to object invisible to actor", %{conn: conn} do + user = insert(:user) + stranger = insert(:user, local: true) + {:ok, post} = CommonAPI.post(user, %{status: "cofe", visibility: "private"}) + + assert Pleroma.Web.ActivityPub.Visibility.private?(post) + + post_object = Object.normalize(post, fetch: false) + + data = %{ + type: "EmojiReact", + object: %{ + id: post_object.data["id"] + }, + content: "😀" + } + + conn = + conn + |> assign(:user, stranger) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{stranger.nickname}/outbox", data) + + assert json_response(conn, 403) + end + test "it increases like count when receiving a like action", %{conn: conn} do note_activity = insert(:note_activity) note_object = Object.normalize(note_activity, fetch: false)