From f28715d3703b3ab860b4735eb86dd3fd1a9a99a8 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 20 May 2026 09:23:29 +0200 Subject: [PATCH 1/3] Fix custom emoji selection (#39088) --- app/javascript/mastodon/actions/compose.js | 2 +- .../features/compose/components/emoji_picker_dropdown.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 2bf1ae9cf1..f487545444 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -671,7 +671,7 @@ export function selectComposeSuggestion(position, token, suggestion, path) { let completion, startPosition; if (suggestion.type === 'emoji') { - completion = suggestion.native || suggestion.colons; + completion = suggestion.native || `:${suggestion.id}:`; startPosition = position - 1; dispatch(useEmoji(suggestion)); diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx index 0013db281a..3da7bf1812 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx @@ -228,7 +228,7 @@ class EmojiPickerMenuImpl extends PureComponent { handleClick = (emoji, event) => { if (!emoji.native) { - emoji.native = emoji.colons; + emoji.native = `:${emoji.id}:`; } if (!(event.ctrlKey || event.metaKey)) { From 22203f8aeb03e8f14dc62e253e83db39825a5bcf Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 20 May 2026 09:55:17 +0200 Subject: [PATCH 2/3] Improve collection item verification (#39096) --- .../process_featured_item_service.rb | 11 ++++++++-- .../verify_featured_item_service.rb | 13 +++++++++--- .../process_featured_item_service_spec.rb | 11 ++++++++++ .../verify_featured_item_service_spec.rb | 21 +++++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/app/services/activitypub/process_featured_item_service.rb b/app/services/activitypub/process_featured_item_service.rb index 245133607c..c1470beeff 100644 --- a/app/services/activitypub/process_featured_item_service.rb +++ b/app/services/activitypub/process_featured_item_service.rb @@ -11,7 +11,10 @@ class ActivityPub::ProcessFeaturedItemService @collection = collection @request_id = request_id @item_json = uri_or_object.is_a?(String) ? fetch_resource(uri_or_object, true) : uri_or_object + @actor_uri = value_or_id(@item_json['featuredObject']) + @approval_uri = value_or_id(@item_json['featureAuthorization']) return if non_matching_uri_hosts?(@collection.uri, @item_json['id']) + return if non_matching_actor_and_approval_uris? with_redis_lock("collection_item:#{@item_json['id']}") do @collection_item = existing_item || pre_approved_item || new_item @@ -22,8 +25,6 @@ class ActivityPub::ProcessFeaturedItemService object_uri: value_or_id(@item_json['featuredObject']) ) - @approval_uri = @item_json['featureAuthorization'] - verify_authorization! unless @collection_item&.account&.local? @collection_item @@ -48,6 +49,12 @@ class ActivityPub::ProcessFeaturedItemService ) end + def non_matching_actor_and_approval_uris? + return false if ActivityPub::TagManager.instance.local_uri?(@actor_uri) + + non_matching_uri_hosts?(@actor_uri, @approval_uri) + end + def verify_authorization! ActivityPub::VerifyFeaturedItemService.new.call(@collection_item, @approval_uri, request_id: @request_id) rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS diff --git a/app/services/activitypub/verify_featured_item_service.rb b/app/services/activitypub/verify_featured_item_service.rb index 6ce524870c..a626874049 100644 --- a/app/services/activitypub/verify_featured_item_service.rb +++ b/app/services/activitypub/verify_featured_item_service.rb @@ -12,8 +12,11 @@ class ActivityPub::VerifyFeaturedItemService return end - return if non_matching_uri_hosts?(approval_uri, @authorization['interactionTarget']) - return unless matching_type? && matching_collection_uri? + @collection_uri = value_or_id(@authorization['interactingObject']) + @actor_uri = value_or_id(@authorization['interactionTarget']) + + return if non_matching_uri_hosts?(approval_uri, @actor_uri) + return unless matching_type? && matching_collection_uri? && matching_actors? account = Account.where(uri: @collection_item.object_uri).first account ||= ActivityPub::FetchRemoteAccountService.new.call(@collection_item.object_uri, request_id:) @@ -29,6 +32,10 @@ class ActivityPub::VerifyFeaturedItemService end def matching_collection_uri? - @collection_item.collection.uri == @authorization['interactingObject'] + @collection_item.collection.uri == @collection_uri + end + + def matching_actors? + @collection_item.object_uri == @actor_uri end end diff --git a/spec/services/activitypub/process_featured_item_service_spec.rb b/spec/services/activitypub/process_featured_item_service_spec.rb index d21b4ccea5..637c06c5ac 100644 --- a/spec/services/activitypub/process_featured_item_service_spec.rb +++ b/spec/services/activitypub/process_featured_item_service_spec.rb @@ -40,6 +40,17 @@ RSpec.describe ActivityPub::ProcessFeaturedItemService do end.to_not change(CollectionItem, :count) end end + + context 'when the actor URI does not match the approval URI' do + let(:featured_object_uri) { 'https://example.com/actor/1' } + let(:feature_authorization_uri) { 'https://other.example.com/auth/1' } + + it 'does not create a collection item and returns `nil`' do + expect do + expect(subject.call(collection, object, position:)).to be_nil + end.to_not change(CollectionItem, :count) + end + end end context 'when the collection item is inlined' do diff --git a/spec/services/activitypub/verify_featured_item_service_spec.rb b/spec/services/activitypub/verify_featured_item_service_spec.rb index f0f1661b6d..3f707394ff 100644 --- a/spec/services/activitypub/verify_featured_item_service_spec.rb +++ b/spec/services/activitypub/verify_featured_item_service_spec.rb @@ -85,4 +85,25 @@ RSpec.describe ActivityPub::VerifyFeaturedItemService do expect(collection_item).to be_rejected end end + + context 'when the authorization references a different account' do + let(:verification_json) do + { + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'FeatureAuthorization', + 'id' => approval_uri, + 'interactionTarget' => 'https://example.com/actor/2', + 'interactingObject' => collection.uri, + } + end + + before { featured_account } + + it 'does not verify the item' do + subject.call(collection_item, approval_uri) + + expect(collection_item.account_id).to be_nil + expect(collection_item).to be_pending + end + end end From 6f8558a6b96041541d911439421563c1e9e7b3f4 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 20 May 2026 11:04:50 +0200 Subject: [PATCH 3/3] Fix `Request` error when issuing a request which host is an IP address (#39030) --- app/lib/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/request.rb b/app/lib/request.rb index d405fb5009..5cbecd7b00 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -290,7 +290,7 @@ class Request addresses = [] begin - addresses = [IPAddr.new(host)] + addresses = [IPAddr.new(host).to_s] rescue IPAddr::InvalidAddressError resolvers = [Resolv::Hosts.new, Resolv::DNS.new.tap { |dns| dns.timeouts = 5 }] addresses = Resolv.new(resolvers).getaddresses(host)