Skip to content

Commit eca84d1

Browse files
Merge pull request #3359 from ClearlyClaire/glitch-soc/merge-4.3
Merge upstream changes up to fa553d8 into stable-4.3
2 parents 9cd5a9a + 3bab7a5 commit eca84d1

File tree

16 files changed

+85
-16
lines changed

16 files changed

+85
-16
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [4.3.18] - 2026-01-20
6+
7+
### Security
8+
9+
- Fix missing limits on various federated properties [GHSA-gg8q-rcg7-p79g](https://github.com/mastodon/mastodon/security/advisories/GHSA-gg8q-rcg7-p79g)
10+
- Fix remote user suspension bypass [GHSA-5h2f-wg8j-xqwp](https://github.com/mastodon/mastodon/security/advisories/GHSA-5h2f-wg8j-xqwp)
11+
- Fix missing length limits on some user-provided fields [GHSA-6x3w-9g92-gvf3](https://github.com/mastodon/mastodon/security/advisories/GHSA-6x3w-9g92-gvf3)
12+
- Fix missing access check for push notification settings update [GHSA-f3q8-7vw3-69v4](https://github.com/mastodon/mastodon/security/advisories/GHSA-f3q8-7vw3-69v4)
13+
14+
### Fixed
15+
16+
- Fix `FeedManager#filter_from_home` error when handling a reblog of a deleted status (#37486 by @ClearlyClaire)
17+
- Fix needlessly complicated SQL query in status batch removal (#37469 by @ClearlyClaire)
18+
- Fix `Vary` parsing in cache control enforcement (#37426 by @MegaManSec)
19+
- Fix thread-unsafe ActivityPub activity dispatch (#37423 by @MegaManSec)
20+
- Fix SignatureParser accepting duplicate parameters in HTTP Signature header (#37375 by @shleeable)
21+
522
## [4.3.17] - 2026-01-07
623

724
### Security

FEDERATION.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,22 @@ Mastodon requires all `POST` requests to be signed, and MAY require `GET` reques
4747
### Additional documentation
4848

4949
- [Mastodon documentation](https://docs.joinmastodon.org/)
50+
51+
## Size limits
52+
53+
Mastodon imposes a few hard limits on federated content.
54+
These limits are intended to be very generous and way above what the Mastodon user experience is optimized for, so as to accomodate future changes and unusual or unforeseen usage patterns, while still providing some limits for performance reasons.
55+
The following table attempts to summary those limits.
56+
57+
| Limited property | Size limit | Consequence of exceeding the limit |
58+
| ------------------------------------------------------------- | ---------- | ---------------------------------- |
59+
| Serialized JSON-LD | 1MB | **Activity is rejected/dropped** |
60+
| Profile fields (actor `PropertyValue` attachments) name/value | 2047 | Field name/value is truncated |
61+
| Number of profile fields (actor `PropertyValue` attachments) | 50 | Fields list is truncated |
62+
| Poll options (number of `anyOf`/`oneOf` in a `Question`) | 500 | Items list is truncated |
63+
| Account username (actor `preferredUsername`) length | 2048 | **Actor will be rejected** |
64+
| Account display name (actor `name`) length | 2048 | Display name will be truncated |
65+
| Account note (actor `summary`) length | 20kB | Account note will be truncated |
66+
| Account `attributionDomains` | 256 | List will be truncated |
67+
| Account aliases (actor `alsoKnownAs`) | 256 | List will be truncated |
68+
| Custom emoji shortcode (`Emoji` `name`) | 2048 | Emoji will be rejected |

app/controllers/activitypub/inboxes_controller.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
class ActivityPub::InboxesController < ActivityPub::BaseController
44
include JsonLdHelper
55

6+
before_action :skip_large_payload
67
before_action :skip_unknown_actor_activity
78
before_action :require_actor_signature!
89
skip_before_action :authenticate_user!
@@ -16,6 +17,10 @@ def create
1617

1718
private
1819

20+
def skip_large_payload
21+
head 413 if request.content_length > ActivityPub::Activity::MAX_JSON_SIZE
22+
end
23+
1924
def skip_unknown_actor_activity
2025
head 202 if unknown_affected_account?
2126
end

app/controllers/api/web/push_subscriptions_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def update_session_with_subscription
5555
end
5656

5757
def set_push_subscription
58-
@push_subscription = ::Web::PushSubscription.find(params[:id])
58+
@push_subscription = ::Web::PushSubscription.where(user_id: active_session.user_id).find(params[:id])
5959
end
6060

6161
def subscription_params

app/lib/activitypub/activity.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class ActivityPub::Activity
55
include Redisable
66
include Lockable
77

8+
MAX_JSON_SIZE = 1.megabyte
89
SUPPORTED_TYPES = %w(Note Question).freeze
910
CONVERTED_TYPES = %w(Image Audio Video Article Page Event).freeze
1011

app/lib/activitypub/activity/update.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def update_status
3232
@status = Status.find_by(uri: object_uri, account_id: @account.id)
3333

3434
# Ignore updates for old unknown objects, since those are updates we are not interested in
35-
return if @status.nil? && object_too_old?
35+
# Also ignore unknown objects from suspended users for the same reasons
36+
return if @status.nil? && (@account.suspended? || object_too_old?)
3637

3738
# We may be getting `Create` and `Update` out of order
3839
@status ||= ActivityPub::Activity::Create.new(@json, @account, **@options).perform

app/lib/activitypub/parser/poll_parser.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
class ActivityPub::Parser::PollParser
44
include JsonLdHelper
55

6+
# Limit the number of items for performance purposes.
7+
# We truncate rather than error out to avoid missing the post entirely.
8+
MAX_ITEMS = 500
9+
610
def initialize(json)
711
@json = json
812
end
@@ -48,6 +52,6 @@ def cached_tallies
4852
private
4953

5054
def items
51-
@json['anyOf'] || @json['oneOf']
55+
(@json['anyOf'] || @json['oneOf'])&.take(MAX_ITEMS)
5256
end
5357
end

app/models/account.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ class Account < ApplicationRecord
7676
DISPLAY_NAME_LENGTH_LIMIT = (ENV['MAX_DISPLAY_NAME_CHARS'] || 30).to_i
7777
NOTE_LENGTH_LIMIT = (ENV['MAX_BIO_CHARS'] || 500).to_i
7878

79+
# Hard limits for federated content
80+
USERNAME_LENGTH_HARD_LIMIT = 2048
81+
DISPLAY_NAME_LENGTH_HARD_LIMIT = 2048
82+
NOTE_LENGTH_HARD_LIMIT = 20.kilobytes
83+
ATTRIBUTION_DOMAINS_HARD_LIMIT = 256
84+
ALSO_KNOWN_AS_HARD_LIMIT = 256
85+
7986
AUTOMATED_ACTOR_TYPES = %w(Application Service).freeze
8087

8188
include Attachmentable # Load prior to Avatar & Header concerns
@@ -103,7 +110,7 @@ class Account < ApplicationRecord
103110
validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? }
104111

105112
# Remote user validations, also applies to internal actors
106-
validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (!local? || actor_type == 'Application') && will_save_change_to_username? }
113+
validates :username, format: { with: USERNAME_ONLY_RE }, length: { maximum: USERNAME_LENGTH_HARD_LIMIT }, if: -> { (!local? || actor_type == 'Application') && will_save_change_to_username? }
107114

108115
# Remote user validations
109116
validates :uri, presence: true, unless: :local?, on: :create

app/models/custom_emoji.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class CustomEmoji < ApplicationRecord
2727
LOCAL_LIMIT = (ENV['MAX_EMOJI_SIZE'] || 256.kilobytes).to_i
2828
LIMIT = [LOCAL_LIMIT, (ENV['MAX_REMOTE_EMOJI_SIZE'] || 256.kilobytes).to_i].max
2929
MINIMUM_SHORTCODE_SIZE = 2
30+
MAX_SHORTCODE_SIZE = 128
31+
MAX_FEDERATED_SHORTCODE_SIZE = 2048
3032

3133
SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}'
3234

@@ -48,7 +50,8 @@ class CustomEmoji < ApplicationRecord
4850
validates_attachment :image, content_type: { content_type: IMAGE_MIME_TYPES }, presence: true
4951
validates_attachment_size :image, less_than: LIMIT, unless: :local?
5052
validates_attachment_size :image, less_than: LOCAL_LIMIT, if: :local?
51-
validates :shortcode, uniqueness: { scope: :domain }, format: { with: SHORTCODE_ONLY_RE }, length: { minimum: MINIMUM_SHORTCODE_SIZE }
53+
validates :shortcode, uniqueness: { scope: :domain }, format: { with: SHORTCODE_ONLY_RE }, length: { minimum: MINIMUM_SHORTCODE_SIZE, maximum: MAX_FEDERATED_SHORTCODE_SIZE }
54+
validates :shortcode, length: { maximum: MAX_SHORTCODE_SIZE }, if: :local?
5255

5356
scope :local, -> { where(domain: nil) }
5457
scope :remote, -> { where.not(domain: nil) }

app/models/custom_filter.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class CustomFilter < ApplicationRecord
3030

3131
EXPIRATION_DURATIONS = [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].freeze
3232

33+
TITLE_LENGTH_LIMIT = 256
34+
3335
include Expireable
3436
include Redisable
3537

@@ -41,6 +43,7 @@ class CustomFilter < ApplicationRecord
4143
accepts_nested_attributes_for :keywords, reject_if: :all_blank, allow_destroy: true
4244

4345
validates :title, :context, presence: true
46+
validates :title, length: { maximum: TITLE_LENGTH_LIMIT }
4447
validate :context_must_be_valid
4548

4649
normalizes :context, with: ->(context) { context.map(&:strip).filter_map(&:presence) }

0 commit comments

Comments
 (0)