Compare commits

..

29 Commits

Author SHA1 Message Date
Claire
04903a45ce Merge pull request #3358 from ClearlyClaire/glitch-soc/merge-4.4
Merge upstream changes up to cbb1085855 into stable-4.4
2026-01-20 16:25:22 +01:00
Claire
d9bad1c407 Merge commit 'cbb10858555d3cbfde24d52859e2d690526c8173' into glitch-soc/merge-4.4 2026-01-20 15:58:26 +01:00
Claire
cbb1085855 Bump version to v4.4.12 (#37547) 2026-01-20 15:53:43 +01:00
Claire
3920feb8bd Merge commit from fork
* Add limit on inbox payload size

The 1MB limit is consistent with the limit we use when fetching remote resources

* Add limit to number of options from federated polls

* Add a limit to the number of federated profile fields

* Add limit on federated username length

* Add hard limits for federated display name and account bio

* Add hard limits for `alsoKnownAs` and `attributionDomains`

* Add hard limit on federated custom emoji shortcode

* Highlight most destructive limits and expand on their reasoning
2026-01-20 15:14:45 +01:00
Claire
4dbe15654a Merge commit from fork 2026-01-20 15:13:42 +01:00
Claire
27e06cdf20 Merge commit from fork 2026-01-20 15:13:10 +01:00
Claire
6ac8b52ccc Merge commit from fork 2026-01-20 15:10:38 +01:00
Claire
7ee99bbe81 Fix potential duplicate handling of quote accept/reject/delete (#37537) 2026-01-20 08:57:40 +01:00
Claire
6c1e77ff1f Skip tombstone creation on deleting from 404 (#37533) 2026-01-20 08:57:40 +01:00
Claire
2588d1ab47 Merge pull request #3354 from ClearlyClaire/glitch-soc/merge-4.4
Merge upstream changes up to 92be0fd12e into stable-4.4
2026-01-19 19:30:24 +01:00
Claire
c0f8640252 Merge commit '92be0fd12e78e7875107c04f239c57043e1876f5' into glitch-soc/merge-4.4 2026-01-19 18:51:52 +01:00
Claire
92be0fd12e Disable rubocop rule disabled in main 2026-01-19 11:37:44 +01:00
Claire
8450ebc7e8 Fix FeedManager#filter_from_home error when handling a reblog of a deleted status (#37486) 2026-01-19 11:37:44 +01:00
Claire
3d27ec34ac Simplify status batch removal SQL query (#37469) 2026-01-19 11:37:44 +01:00
Joshua Rogers
d3551e1ab6 Fix Vary parsing in cache control enforcement (#37426) 2026-01-19 11:37:44 +01:00
Joshua Rogers
0b9c741dac Fix thread-unsafe ActivityPub activity dispatch (#37423) 2026-01-19 11:37:44 +01:00
Shlee
a8c9923df9 Fix SignatureParser accepting duplicate parameters in HTTP Signature header (#37375)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2026-01-19 11:37:44 +01:00
Shlee
f32067dc56 SharedConnectionPool - NoMethodError: undefined method 'site' for Integer (#37374) 2026-01-19 11:37:44 +01:00
Claire
36981fadf0 Update SECURITY.md (#37504) 2026-01-15 14:17:36 +01:00
Claire
b60edf59d8 Merge pull request #3337 from ClearlyClaire/glitch-soc/merge-4.4
Merge upstream changes up to ef4d722d6a to stable-4.4
2026-01-07 15:22:45 +01:00
Claire
3a87a63abf Merge commit 'ef4d722d6af8e0e9a25e30bb6c33aa0e14f6951f' into glitch-soc/merge-4.4 2026-01-07 14:46:45 +01:00
Claire
ef4d722d6a Bump version to v4.4.11 (#37410) 2026-01-07 14:45:02 +01:00
Claire
68e30985ca Merge commit from fork 2026-01-07 14:15:13 +01:00
Claire
1702290786 Merge commit from fork 2026-01-07 14:14:42 +01:00
Claire
abb3a02ce2 Merge pull request #3325 from ClearlyClaire/glitch-soc/merge-4.4
Merge upstream changes up to f5890040e1
2025-12-28 19:47:33 +01:00
Claire
25c79f526a Merge commit 'f5890040e12169e2ec27be68033ab0f1ad744c6e' into glitch-soc/merge-4.4 2025-12-28 11:19:25 +01:00
Claire
f5890040e1 Fix mentions of domain-blocked users being processed (#37257) 2025-12-19 11:00:14 +01:00
Claire
740f262e38 Change HTTP Signature verification status from 401 to 503 on temporary failure to get remote actor (#37221) 2025-12-19 11:00:14 +01:00
Claire
4d2b6795a4 Fix stable-4.4 branches being built with the latest tag (#37170) 2025-12-08 18:30:42 +01:00
35 changed files with 196 additions and 50 deletions

View File

@@ -20,7 +20,7 @@ jobs:
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.4.') }}
latest=false
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}
@@ -37,7 +37,7 @@ jobs:
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.4.') }}
latest=false
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}

View File

@@ -21,11 +21,11 @@ Metrics/BlockNesting:
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/CyclomaticComplexity:
Max: 25
Enabled: false
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/PerceivedComplexity:
Max: 27
Enabled: false
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowedVars, DefaultToNil.

View File

@@ -2,6 +2,43 @@
All notable changes to this project will be documented in this file.
## [4.4.12] - 2026-01-20
### Security
- Fix missing limits on various federated properties [GHSA-gg8q-rcg7-p79g](https://github.com/mastodon/mastodon/security/advisories/GHSA-gg8q-rcg7-p79g)
- Fix remote user suspension bypass [GHSA-5h2f-wg8j-xqwp](https://github.com/mastodon/mastodon/security/advisories/GHSA-5h2f-wg8j-xqwp)
- Fix missing length limits on some user-provided fields [GHSA-6x3w-9g92-gvf3](https://github.com/mastodon/mastodon/security/advisories/GHSA-6x3w-9g92-gvf3)
- Fix missing access check for push notification settings update [GHSA-f3q8-7vw3-69v4](https://github.com/mastodon/mastodon/security/advisories/GHSA-f3q8-7vw3-69v4)
### Changed
- Skip tombstone creation on deleting from 404 (#37533 by @ClearlyClaire)
### Fixed
- Fix potential duplicate handling of quote accept/reject/delete (#37537 by @ClearlyClaire)
- Fix `FeedManager#filter_from_home` error when handling a reblog of a deleted status (#37486 by @ClearlyClaire)
- Fix needlessly complicated SQL query in status batch removal (#37469 by @ClearlyClaire)
- Fix `Vary` parsing in cache control enforcement (#37426 by @MegaManSec)
- Fix thread-unsafe ActivityPub activity dispatch (#37423 by @MegaManSec)
- Fix SignatureParser accepting duplicate parameters in HTTP Signature header (#37375 by @shleeable)
## [4.4.11] - 2026-01-07
### Security
- Fix SSRF protection bypass ([GHSA](https://github.com/mastodon/mastodon/security/advisories/GHSA-xfrj-c749-jxxq))
- Fix missing ownership check in severed relationships controller ([GHSA](https://github.com/mastodon/mastodon/security/advisories/GHSA-ww85-x9cp-5v24))
### Changed
- Change HTTP Signature verification status from 401 to 503 on temporary failure to get remote actor (#37221 by @ClearlyClaire)
### Fixed
- Fix mentions of domain-blocked users being processed (#37257 by @ClearlyClaire)
## [4.4.10] - 2025-12-08
### Security

View File

@@ -48,3 +48,22 @@ Mastodon requires all `POST` requests to be signed, and MAY require `GET` reques
### Additional documentation
- [Mastodon documentation](https://docs.joinmastodon.org/)
## Size limits
Mastodon imposes a few hard limits on federated content.
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.
The following table attempts to summary those limits.
| Limited property | Size limit | Consequence of exceeding the limit |
| ------------------------------------------------------------- | ---------- | ---------------------------------- |
| Serialized JSON-LD | 1MB | **Activity is rejected/dropped** |
| Profile fields (actor `PropertyValue` attachments) name/value | 2047 | Field name/value is truncated |
| Number of profile fields (actor `PropertyValue` attachments) | 50 | Fields list is truncated |
| Poll options (number of `anyOf`/`oneOf` in a `Question`) | 500 | Items list is truncated |
| Account username (actor `preferredUsername`) length | 2048 | **Actor will be rejected** |
| Account display name (actor `name`) length | 2048 | Display name will be truncated |
| Account note (actor `summary`) length | 20kB | Account note will be truncated |
| Account `attributionDomains` | 256 | List will be truncated |
| Account aliases (actor `alsoKnownAs`) | 256 | List will be truncated |
| Custom emoji shortcode (`Emoji` `name`) | 2048 | Emoji will be rejected |

View File

@@ -17,5 +17,4 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
| ------- | ---------------- |
| 4.4.x | Yes |
| 4.3.x | Until 2026-05-06 |
| 4.2.x | Until 2026-01-08 |
| < 4.2 | No |
| < 4.3 | No |

View File

@@ -3,6 +3,7 @@
class ActivityPub::InboxesController < ActivityPub::BaseController
include JsonLdHelper
before_action :skip_large_payload
before_action :skip_unknown_actor_activity
before_action :require_actor_signature!
skip_before_action :authenticate_user!
@@ -16,6 +17,10 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
private
def skip_large_payload
head 413 if request.content_length > ActivityPub::Activity::MAX_JSON_SIZE
end
def skip_unknown_actor_activity
head 202 if unknown_affected_account?
end

View File

@@ -62,7 +62,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end
def set_push_subscription
@push_subscription = ::Web::PushSubscription.find(params[:id])
@push_subscription = ::Web::PushSubscription.where(user_id: active_session.user_id).find(params[:id])
end
def subscription_params

View File

@@ -19,7 +19,7 @@ module CacheConcern
# from being used as cache keys, while allowing to `Vary` on them (to not serve
# anonymous cached data to authenticated requests when authentication matters)
def enforce_cache_control!
vary = response.headers['Vary']&.split&.map { |x| x.strip.downcase }
vary = response.headers['Vary'].to_s.split(',').map { |x| x.strip.downcase }.reject(&:empty?)
return unless vary.present? && %w(cookie authorization signature).any? { |header| vary.include?(header) && request.headers[header].present? }
response.cache_control.replace(private: true, no_store: true)

View File

@@ -70,10 +70,13 @@ module SignatureVerification
rescue Mastodon::SignatureVerificationError => e
fail_with! e.message
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
@signature_verification_failure_code ||= 503
fail_with! "Failed to fetch remote data: #{e.message}"
rescue Mastodon::UnexpectedResponseError
@signature_verification_failure_code ||= 503
fail_with! 'Failed to fetch remote data (got unexpected reply from server)'
rescue Stoplight::Error::RedLight
@signature_verification_failure_code ||= 503
fail_with! 'Fetching attempt skipped because of recent connection failure'
end

View File

@@ -26,7 +26,7 @@ class SeveredRelationshipsController < ApplicationController
private
def set_event
@event = AccountRelationshipSeveranceEvent.find(params[:id])
@event = AccountRelationshipSeveranceEvent.where(account: current_account).find(params[:id])
end
def following_data

View File

@@ -5,6 +5,7 @@ class ActivityPub::Activity
include Redisable
include Lockable
MAX_JSON_SIZE = 1.megabyte
SUPPORTED_TYPES = %w(Note Question).freeze
CONVERTED_TYPES = %w(Image Audio Video Article Page Event).freeze
@@ -21,14 +22,13 @@ class ActivityPub::Activity
class << self
def factory(json, account, **)
@json = json
klass&.new(json, account, **)
klass_for(json)&.new(json, account, **)
end
private
def klass
case @json['type']
def klass_for(json)
case json['type']
when 'Create'
ActivityPub::Activity::Create
when 'Announce'

View File

@@ -56,7 +56,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
end
def revoke_quote
@quote = Quote.find_by(approval_uri: object_uri, quoted_account: @account)
@quote = Quote.find_by(approval_uri: object_uri, quoted_account: @account, state: [:pending, :accepted])
return if @quote.nil?
ActivityPub::Forwarder.new(@account, @json, @quote.status).forward! if @quote.status.present?

View File

@@ -32,7 +32,8 @@ class ActivityPub::Activity::Update < ActivityPub::Activity
@status = Status.find_by(uri: object_uri, account_id: @account.id)
# Ignore updates for old unknown objects, since those are updates we are not interested in
return if @status.nil? && object_too_old?
# Also ignore unknown objects from suspended users for the same reasons
return if @status.nil? && (@account.suspended? || object_too_old?)
# We may be getting `Create` and `Update` out of order
@status ||= ActivityPub::Activity::Create.new(@json, @account, **@options).perform

View File

@@ -3,6 +3,10 @@
class ActivityPub::Parser::PollParser
include JsonLdHelper
# Limit the number of items for performance purposes.
# We truncate rather than error out to avoid missing the post entirely.
MAX_ITEMS = 500
def initialize(json)
@json = json
end
@@ -48,6 +52,6 @@ class ActivityPub::Parser::PollParser
private
def items
@json['anyOf'] || @json['oneOf']
(@json['anyOf'] || @json['oneOf'])&.take(MAX_ITEMS)
end
end

View File

@@ -41,12 +41,17 @@ class ConnectionPool::SharedConnectionPool < ConnectionPool
# ConnectionPool 2.4+ calls `checkin(force: true)` after fork.
# When this happens, we should remove all connections from Thread.current
::Thread.current.keys.each do |name| # rubocop:disable Style/HashEachMethods
next unless name.to_s.start_with?("#{@key}-")
connection_keys = ::Thread.current.keys.select { |key| key.to_s.start_with?("#{@key}-") && !key.to_s.start_with?("#{@key_count}-") }
count_keys = ::Thread.current.keys.select { |key| key.to_s.start_with?("#{@key_count}-") }
@available.push(::Thread.current[name])
::Thread.current[name] = nil
connection_keys.each do |key|
@available.push(::Thread.current[key])
::Thread.current[key] = nil
end
count_keys.each do |key|
::Thread.current[key] = nil
end
elsif ::Thread.current[key(preferred_tag)]
if ::Thread.current[key_count(preferred_tag)] == 1
@available.push(::Thread.current[key(preferred_tag)])

View File

@@ -500,6 +500,7 @@ class FeedManager
return :filter if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
return :skip_home if timeline_type != :list && crutches[:exclusive_list_users][status.account_id].present?
return :filter if crutches[:languages][status.account_id].present? && status.language.present? && !crutches[:languages][status.account_id].include?(status.language)
return :filter if status.reblog? && status.reblog.blank?
check_for_blocks = crutches[:active_mentions][status.id] || []
check_for_blocks.push(status.account_id)

View File

@@ -1,9 +1,7 @@
# frozen_string_literal: true
module PrivateAddressCheck
module_function
CIDR_LIST = [
IP4_CIDR_LIST = [
IPAddr.new('0.0.0.0/8'), # Current network (only valid as source address)
IPAddr.new('100.64.0.0/10'), # Shared Address Space
IPAddr.new('172.16.0.0/12'), # Private network
@@ -16,6 +14,9 @@ module PrivateAddressCheck
IPAddr.new('224.0.0.0/4'), # IP multicast (former Class D network)
IPAddr.new('240.0.0.0/4'), # Reserved (former Class E network)
IPAddr.new('255.255.255.255'), # Broadcast
].freeze
CIDR_LIST = (IP4_CIDR_LIST + IP4_CIDR_LIST.map(&:ipv4_mapped) + [
IPAddr.new('64:ff9b::/96'), # IPv4/IPv6 translation (RFC 6052)
IPAddr.new('100::/64'), # Discard prefix (RFC 6666)
IPAddr.new('2001::/32'), # Teredo tunneling
@@ -25,7 +26,9 @@ module PrivateAddressCheck
IPAddr.new('2002::/16'), # 6to4
IPAddr.new('fc00::/7'), # Unique local address
IPAddr.new('ff00::/8'), # Multicast
].freeze
]).freeze
module_function
def private_address?(address)
address.private? || address.loopback? || address.link_local? || CIDR_LIST.any? { |cidr| cidr.include?(address) }

View File

@@ -25,9 +25,13 @@ class SignatureParser
# Use `skip` instead of `scan` as we only care about the subgroups
while scanner.skip(PARAM_RE)
key = scanner[:key]
# Detect a duplicate key
raise Mastodon::SignatureVerificationError, 'Error parsing signature with duplicate keys' if params.key?(key)
# This is not actually correct with regards to quoted pairs, but it's consistent
# with our previous implementation, and good enough in practice.
params[scanner[:key]] = scanner[:value] || scanner[:quoted_value][1...-1]
params[key] = scanner[:value] || scanner[:quoted_value][1...-1]
scanner.skip(/\s*/)
return params if scanner.eos?

View File

@@ -78,6 +78,13 @@ class Account < ApplicationRecord
DISPLAY_NAME_LENGTH_LIMIT = (ENV['MAX_DISPLAY_NAME_CHARS'] || 30).to_i
NOTE_LENGTH_LIMIT = (ENV['MAX_BIO_CHARS'] || 500).to_i
# Hard limits for federated content
USERNAME_LENGTH_HARD_LIMIT = 2048
DISPLAY_NAME_LENGTH_HARD_LIMIT = 2048
NOTE_LENGTH_HARD_LIMIT = 20.kilobytes
ATTRIBUTION_DOMAINS_HARD_LIMIT = 256
ALSO_KNOWN_AS_HARD_LIMIT = 256
AUTOMATED_ACTOR_TYPES = %w(Application Service).freeze
include Attachmentable # Load prior to Avatar & Header concerns
@@ -109,7 +116,7 @@ class Account < ApplicationRecord
validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? }
# Remote user validations, also applies to internal actors
validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (remote? || actor_type_application?) && will_save_change_to_username? }
validates :username, format: { with: USERNAME_ONLY_RE }, length: { maximum: USERNAME_LENGTH_HARD_LIMIT }, if: -> { (remote? || actor_type_application?) && will_save_change_to_username? }
# Remote user validations
validates :uri, presence: true, unless: :local?, on: :create

View File

@@ -27,6 +27,8 @@ class CustomEmoji < ApplicationRecord
LOCAL_LIMIT = (ENV['MAX_EMOJI_SIZE'] || 256.kilobytes).to_i
LIMIT = [LOCAL_LIMIT, (ENV['MAX_REMOTE_EMOJI_SIZE'] || 256.kilobytes).to_i].max
MINIMUM_SHORTCODE_SIZE = 2
MAX_SHORTCODE_SIZE = 128
MAX_FEDERATED_SHORTCODE_SIZE = 2048
SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}'
@@ -48,7 +50,8 @@ class CustomEmoji < ApplicationRecord
validates_attachment :image, content_type: { content_type: IMAGE_MIME_TYPES }, presence: true
validates_attachment_size :image, less_than: LIMIT, unless: :local?
validates_attachment_size :image, less_than: LOCAL_LIMIT, if: :local?
validates :shortcode, uniqueness: { scope: :domain }, format: { with: SHORTCODE_ONLY_RE }, length: { minimum: MINIMUM_SHORTCODE_SIZE }
validates :shortcode, uniqueness: { scope: :domain }, format: { with: SHORTCODE_ONLY_RE }, length: { minimum: MINIMUM_SHORTCODE_SIZE, maximum: MAX_FEDERATED_SHORTCODE_SIZE }
validates :shortcode, length: { maximum: MAX_SHORTCODE_SIZE }, if: :local?
scope :local, -> { where(domain: nil) }
scope :remote, -> { where.not(domain: nil) }

View File

@@ -30,6 +30,8 @@ class CustomFilter < ApplicationRecord
EXPIRATION_DURATIONS = [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].freeze
TITLE_LENGTH_LIMIT = 256
include Expireable
include Redisable
@@ -41,6 +43,7 @@ class CustomFilter < ApplicationRecord
accepts_nested_attributes_for :keywords, reject_if: :all_blank, allow_destroy: true
validates :title, :context, presence: true
validates :title, length: { maximum: TITLE_LENGTH_LIMIT }
validate :context_must_be_valid
normalizes :context, with: ->(context) { context.map(&:strip).filter_map(&:presence) }

View File

@@ -17,7 +17,9 @@ class CustomFilterKeyword < ApplicationRecord
belongs_to :custom_filter
validates :keyword, presence: true
KEYWORD_LENGTH_LIMIT = 512
validates :keyword, presence: true, length: { maximum: KEYWORD_LENGTH_LIMIT }
alias_attribute :phrase, :keyword

View File

@@ -17,6 +17,7 @@ class List < ApplicationRecord
include Paginable
PER_ACCOUNT_LIMIT = 50
TITLE_LENGTH_LIMIT = 256
enum :replies_policy, { list: 0, followed: 1, none: 2 }, prefix: :show, validate: true
@@ -26,7 +27,7 @@ class List < ApplicationRecord
has_many :accounts, through: :list_accounts
has_many :active_accounts, -> { merge(ListAccount.active) }, through: :list_accounts, source: :account
validates :title, presence: true
validates :title, presence: true, length: { maximum: TITLE_LENGTH_LIMIT }
validate :validate_account_lists_limit, on: :create

View File

@@ -42,9 +42,9 @@ class Quote < ApplicationRecord
def reject!
if accepted?
update!(state: :revoked)
update!(state: :revoked, approval_uri: nil)
elsif !revoked?
update!(state: :rejected)
update!(state: :rejected, approval_uri: nil)
end
end

View File

@@ -92,7 +92,6 @@ class ActivityPub::FetchRemoteStatusService < BaseService
existing_status = Status.remote.find_by(uri: uri)
if existing_status&.distributable?
Rails.logger.debug { "FetchRemoteStatusService - Got 404 for orphaned status with URI #{uri}, deleting" }
Tombstone.find_or_create_by(uri: uri, account: existing_status.account)
RemoveStatusService.new.call(existing_status, redraft: false)
end
end

View File

@@ -6,6 +6,7 @@ class ActivityPub::ProcessAccountService < BaseService
include Redisable
include Lockable
MAX_PROFILE_FIELDS = 50
SUBDOMAINS_RATELIMIT = 10
DISCOVERIES_PER_REQUEST = 400
@@ -122,15 +123,15 @@ class ActivityPub::ProcessAccountService < BaseService
def set_immediate_attributes!
@account.featured_collection_url = valid_collection_uri(@json['featured'])
@account.display_name = @json['name'] || ''
@account.note = @json['summary'] || ''
@account.display_name = (@json['name'] || '')[0...(Account::DISPLAY_NAME_LENGTH_HARD_LIMIT)]
@account.note = (@json['summary'] || '')[0...(Account::NOTE_LENGTH_HARD_LIMIT)]
@account.locked = @json['manuallyApprovesFollowers'] || false
@account.fields = property_values || {}
@account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) }
@account.also_known_as = as_array(@json['alsoKnownAs'] || []).take(Account::ALSO_KNOWN_AS_HARD_LIMIT).map { |item| value_or_id(item) }
@account.discoverable = @json['discoverable'] || false
@account.indexable = @json['indexable'] || false
@account.memorial = @json['memorial'] || false
@account.attribution_domains = as_array(@json['attributionDomains'] || []).map { |item| value_or_id(item) }
@account.attribution_domains = as_array(@json['attributionDomains'] || []).take(Account::ATTRIBUTION_DOMAINS_HARD_LIMIT).map { |item| value_or_id(item) }
end
def set_fetchable_key!
@@ -251,7 +252,10 @@ class ActivityPub::ProcessAccountService < BaseService
def property_values
return unless @json['attachment'].is_a?(Array)
as_array(@json['attachment']).select { |attachment| attachment['type'] == 'PropertyValue' }.map { |attachment| attachment.slice('name', 'value') }
as_array(@json['attachment'])
.select { |attachment| attachment['type'] == 'PropertyValue' }
.take(MAX_PROFILE_FIELDS)
.map { |attachment| attachment.slice('name', 'value') }
end
def mismatching_origin?(url)

View File

@@ -34,7 +34,7 @@ class BatchedRemoveStatusService < BaseService
# transaction lock the database, but we use the delete method instead
# of destroy to avoid all callbacks. We rely on foreign keys to
# cascade the delete faster without loading the associations.
statuses_and_reblogs.each_slice(50) { |slice| Status.where(id: slice.map(&:id)).delete_all }
statuses_and_reblogs.each_slice(50) { |slice| Status.unscoped.where(id: slice.pluck(:id)).delete_all }
# Since we skipped all callbacks, we also need to manually
# deindex the statuses

View File

@@ -14,6 +14,8 @@ class FanOutOnWriteService < BaseService
@account = status.account
@options = options
return if @status.proper.account.suspended?
check_race_condition!
warm_payload_cache!

View File

@@ -71,7 +71,7 @@ class ProcessMentionsService < BaseService
# Make sure we never mention blocked accounts
unless @current_mentions.empty?
mentioned_domains = @current_mentions.filter_map { |m| m.account.domain }.uniq
blocked_domains = Set.new(mentioned_domains.empty? ? [] : AccountDomainBlock.where(account_id: @status.account_id, domain: mentioned_domains))
blocked_domains = Set.new(mentioned_domains.empty? ? [] : AccountDomainBlock.where(account_id: @status.account_id, domain: mentioned_domains).pluck(:domain))
mentioned_account_ids = @current_mentions.map(&:account_id)
blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id))

View File

@@ -59,7 +59,7 @@ services:
web:
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/glitch-soc/mastodon:v4.4.10
image: ghcr.io/glitch-soc/mastodon:v4.4.12
restart: always
env_file: .env.production
command: bundle exec puma -C config/puma.rb
@@ -83,7 +83,7 @@ services:
# build:
# dockerfile: ./streaming/Dockerfile
# context: .
image: ghcr.io/glitch-soc/mastodon-streaming:v4.4.10
image: ghcr.io/glitch-soc/mastodon-streaming:v4.4.12
restart: always
env_file: .env.production
command: node ./streaming/index.js
@@ -102,7 +102,7 @@ services:
sidekiq:
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/glitch-soc/mastodon:v4.4.10
image: ghcr.io/glitch-soc/mastodon:v4.4.12
restart: always
env_file: .env.production
command: bundle exec sidekiq

View File

@@ -13,7 +13,7 @@ module Mastodon
end
def patch
10
12
end
def default_prerelease

View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe PrivateAddressCheck do
describe 'private_address?' do
it 'returns true for private addresses' do
# rubocop:disable RSpec/ExpectActual
expect(
[
'192.168.1.7',
'0.0.0.0',
'127.0.0.1',
'::ffff:0.0.0.1',
]
).to all satisfy('return true') { |addr| described_class.private_address?(IPAddr.new(addr)) }
# rubocop:enable RSpec/ExpectActual
end
end
end

View File

@@ -65,9 +65,10 @@ RSpec.describe 'API Web Push Subscriptions' do
end
describe 'PUT /api/web/push_subscriptions/:id' do
before { sign_in Fabricate :user }
before { sign_in user }
let(:subscription) { Fabricate :web_push_subscription }
let(:user) { Fabricate(:user) }
let(:subscription) { Fabricate(:web_push_subscription, user: user) }
it 'gracefully handles invalid nested params' do
put api_web_push_subscription_path(subscription), params: { data: 'invalid' }

View File

@@ -3,9 +3,10 @@
require 'rails_helper'
RSpec.describe 'Severed Relationships' do
let(:account_rs_event) { Fabricate :account_relationship_severance_event }
let(:account_rs_event) { Fabricate(:account_relationship_severance_event) }
let(:user) { account_rs_event.account.user }
before { sign_in Fabricate(:user) }
before { sign_in user }
describe 'GET /severed_relationships/:id/following' do
it 'returns a CSV file with correct data' do
@@ -22,6 +23,17 @@ RSpec.describe 'Severed Relationships' do
expect(response.body)
.to include('Account address')
end
context 'when the user is not the subject of the event' do
let(:user) { Fabricate(:user) }
it 'returns a 404' do
get following_severed_relationship_path(account_rs_event, format: :csv)
expect(response)
.to have_http_status(404)
end
end
end
describe 'GET /severed_relationships/:id/followers' do
@@ -39,5 +51,16 @@ RSpec.describe 'Severed Relationships' do
expect(response.body)
.to include('Account address')
end
context 'when the user is not the subject of the event' do
let(:user) { Fabricate(:user) }
it 'returns a 404' do
get followers_severed_relationship_path(account_rs_event, format: :csv)
expect(response)
.to have_http_status(404)
end
end
end
end

View File

@@ -8,9 +8,9 @@ RSpec.describe ProcessMentionsService do
let(:account) { Fabricate(:account, username: 'alice') }
context 'when mentions contain blocked accounts' do
let(:non_blocked_account) { Fabricate(:account) }
let(:individually_blocked_account) { Fabricate(:account) }
let(:domain_blocked_account) { Fabricate(:account, domain: 'evil.com') }
let!(:non_blocked_account) { Fabricate(:account) }
let!(:individually_blocked_account) { Fabricate(:account) }
let!(:domain_blocked_account) { Fabricate(:account, domain: 'evil.com', protocol: :activitypub) }
let(:status) { Fabricate(:status, account: account, text: "Hello @#{non_blocked_account.acct} @#{individually_blocked_account.acct} @#{domain_blocked_account.acct}", visibility: :public) }
before do