From 1547d002150fef0823f10ad8414fe1f8eff3a8f0 Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Fri, 13 Mar 2026 22:07:39 +0100 Subject: [PATCH] Harden M3 giftwrap recipient gating in storage adapters --- .../storage/adapters/memory/events.ex | 26 +++++++++++++++-- .../storage/adapters/postgres/events.ex | 29 +++++++++++-------- .../storage/adapters/memory/adapter_test.exs | 22 ++++++++++++++ .../postgres/events_query_count_test.exs | 3 ++ 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/lib/parrhesia/storage/adapters/memory/events.ex b/lib/parrhesia/storage/adapters/memory/events.ex index 2533d6c..d54f12c 100644 --- a/lib/parrhesia/storage/adapters/memory/events.ex +++ b/lib/parrhesia/storage/adapters/memory/events.ex @@ -37,15 +37,19 @@ defmodule Parrhesia.Storage.Adapters.Memory.Events do end @impl true - def query(_context, filters, _opts) do + def query(_context, filters, opts) do with :ok <- Filter.validate_filters(filters) do state = Store.get(& &1) + requester_pubkeys = Keyword.get(opts, :requester_pubkeys, []) events = state.events |> Map.values() - |> Enum.reject(fn event -> MapSet.member?(state.deleted, event["id"]) end) - |> Enum.filter(&Filter.matches_any?(&1, filters)) + |> Enum.filter(fn event -> + not MapSet.member?(state.deleted, event["id"]) and + Filter.matches_any?(event, filters) and + giftwrap_visible_to_requester?(event, requester_pubkeys) + end) {:ok, events} end @@ -100,4 +104,20 @@ defmodule Parrhesia.Storage.Adapters.Memory.Events do @impl true def purge_expired(_opts), do: {:ok, 0} + + defp giftwrap_visible_to_requester?(%{"kind" => 1059} = event, requester_pubkeys) do + requester_pubkeys != [] and + event_targets_any_recipient?(event, requester_pubkeys) + end + + defp giftwrap_visible_to_requester?(_event, _requester_pubkeys), do: true + + defp event_targets_any_recipient?(event, requester_pubkeys) do + event + |> Map.get("tags", []) + |> Enum.any?(fn + ["p", recipient | _rest] -> recipient in requester_pubkeys + _tag -> false + end) + end end diff --git a/lib/parrhesia/storage/adapters/postgres/events.ex b/lib/parrhesia/storage/adapters/postgres/events.ex index 4a97f1b..905ab52 100644 --- a/lib/parrhesia/storage/adapters/postgres/events.ex +++ b/lib/parrhesia/storage/adapters/postgres/events.ex @@ -661,19 +661,24 @@ defmodule Parrhesia.Storage.Adapters.Postgres.Events do defp maybe_restrict_giftwrap_access(query, filter, opts) do requester_pubkeys = Keyword.get(opts, :requester_pubkeys, []) - if targets_giftwrap?(filter) and requester_pubkeys != [] do - where( - query, - [event], - fragment( - "EXISTS (SELECT 1 FROM event_tags AS tag WHERE tag.event_created_at = ? AND tag.event_id = ? AND tag.name = 'p' AND tag.value = ANY(?))", - event.created_at, - event.id, - type(^requester_pubkeys, {:array, :string}) + cond do + targets_giftwrap?(filter) and requester_pubkeys != [] -> + where( + query, + [event], + fragment( + "EXISTS (SELECT 1 FROM event_tags AS tag WHERE tag.event_created_at = ? AND tag.event_id = ? AND tag.name = 'p' AND tag.value = ANY(?))", + event.created_at, + event.id, + type(^requester_pubkeys, {:array, :string}) + ) ) - ) - else - query + + targets_giftwrap?(filter) -> + where(query, [_event], false) + + true -> + query end end diff --git a/test/parrhesia/storage/adapters/memory/adapter_test.exs b/test/parrhesia/storage/adapters/memory/adapter_test.exs index da1e660..753243d 100644 --- a/test/parrhesia/storage/adapters/memory/adapter_test.exs +++ b/test/parrhesia/storage/adapters/memory/adapter_test.exs @@ -25,4 +25,26 @@ defmodule Parrhesia.Storage.Adapters.Memory.AdapterTest do assert :ok = Admin.append_audit_log(%{}, %{method: "ping"}) assert {:ok, [%{method: "ping"}]} = Admin.list_audit_logs(%{}, []) end + + test "memory adapter enforces recipient visibility for giftwrap queries" do + recipient = String.duplicate("b", 64) + + giftwrap_event = %{ + "id" => String.duplicate("c", 64), + "pubkey" => "pk", + "kind" => 1059, + "tags" => [["p", recipient]], + "content" => "ciphertext" + } + + assert {:ok, _event} = Events.put_event(%{}, giftwrap_event) + + filters = [%{"kinds" => [1059], "#p" => [recipient]}] + + assert {:ok, [result]} = Events.query(%{}, filters, requester_pubkeys: [recipient]) + assert result["id"] == giftwrap_event["id"] + + assert {:ok, []} = Events.query(%{}, filters, requester_pubkeys: []) + assert {:ok, 0} = Events.count(%{}, filters, requester_pubkeys: []) + end end diff --git a/test/parrhesia/storage/adapters/postgres/events_query_count_test.exs b/test/parrhesia/storage/adapters/postgres/events_query_count_test.exs index 553f4fd..ac091f8 100644 --- a/test/parrhesia/storage/adapters/postgres/events_query_count_test.exs +++ b/test/parrhesia/storage/adapters/postgres/events_query_count_test.exs @@ -243,6 +243,9 @@ defmodule Parrhesia.Storage.Adapters.Postgres.EventsQueryCountTest do Events.query(%{}, filters, requester_pubkeys: [recipient]) assert result["id"] == allowed["id"] + + assert {:ok, []} = Events.query(%{}, filters, requester_pubkeys: []) + assert {:ok, 0} = Events.count(%{}, filters, requester_pubkeys: []) end test "query/3 supports #i keypackage reference lookups" do