From da47a1bd3cd07935d7ca55a1f4f1a76f7f167e0d Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 27 Jul 2026 16:02:01 +0200 Subject: [PATCH] Merge commit from fork * Fix GHSA-7jvv-fhmg-wpfw * Fix GHSA-vwhj-3g83-v276 * Bump version to v4.4.21 --- CHANGELOG.md | 19 +++++++++++++++++++ .../api/v1/admin/measures_controller.rb | 6 +++--- .../api/v1/admin/retention_controller.rb | 14 +++++++------- .../admin/metrics/dimension/base_dimension.rb | 2 ++ app/lib/admin/metrics/measure/base_measure.rb | 6 ++++-- app/lib/admin/metrics/retention.rb | 7 +++++-- app/lib/private_address_check.rb | 2 +- docker-compose.yml | 6 +++--- lib/mastodon/version.rb | 2 +- spec/requests/api/v1/admin/measures_spec.rb | 2 ++ spec/requests/api/v1/admin/retention_spec.rb | 4 ++-- 11 files changed, 49 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15af4117cba..e627d8d798f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. +## [4.4.21] - 2026-07-27 + +### Security + +- Fix incorrect permission enforcement ([GHSA-7jvv-fhmg-wpfw](https://github.com/mastodon/mastodon/security/advisories/GHSA-7jvv-fhmg-wpfw)) +- Fix SSRF protection bypass via IPv4-compatible IPv6 addresses ([GHSA-vwhj-3g83-v276](https://github.com/mastodon/mastodon/security/advisories/GHSA-vwhj-3g83-v276)) +- Update dependencies + +### Fixed + +- Fix performance of user-focused queries in admin dashboard (#39929 by @ClearlyClaire) +- Fix Web Push subscription deletion endpoint incorrectly expecting anti-CSRF tokens (#39918 by @ClearlyClaire) +- Fix `ActivityPub::Activity::Create` trying to re-create known statuses when author changes (#39916 by @ClearlyClaire) +- Fix lax relevancy check in inbound activity processing (#39892 by @ClearlyClaire) +- Fix `Account::Merging` concern not supporting Quotes, refactor it (#39884 by @ClearlyClaire) +- Fix suspended accounts not being removed from follow request count in `/api/v1/accounts/verify_credentials` (#39858 by @ClearlyClaire) +- Fix followed tags not being properly cleaned up when an account is deleted (#39824 by @shleeable) +- Fix autofollow option being ignored in invite moderation interface (#39819 by @shleeable) + ## [4.4.20] - 2026-06-25 ### Security diff --git a/app/controllers/api/v1/admin/measures_controller.rb b/app/controllers/api/v1/admin/measures_controller.rb index d78d7e10b33..d879cca926b 100644 --- a/app/controllers/api/v1/admin/measures_controller.rb +++ b/app/controllers/api/v1/admin/measures_controller.rb @@ -17,9 +17,9 @@ class Api::V1::Admin::MeasuresController < Api::BaseController def set_measures @measures = Admin::Metrics::Measure.retrieve( - params[:keys], - params[:start_at], - params[:end_at], + params.require(:keys), + params.require(:start_at), + params.require(:end_at), params ) end diff --git a/app/controllers/api/v1/admin/retention_controller.rb b/app/controllers/api/v1/admin/retention_controller.rb index 59d6b838833..41f1be7edbe 100644 --- a/app/controllers/api/v1/admin/retention_controller.rb +++ b/app/controllers/api/v1/admin/retention_controller.rb @@ -4,22 +4,22 @@ class Api::V1::Admin::RetentionController < Api::BaseController include Authorization before_action -> { authorize_if_got_token! :'admin:read' } - before_action :set_cohorts + before_action :set_retention after_action :verify_authorized def create authorize :dashboard, :index? - render json: @cohorts, each_serializer: REST::Admin::CohortSerializer + render json: @retention.cohorts, each_serializer: REST::Admin::CohortSerializer end private - def set_cohorts - @cohorts = Admin::Metrics::Retention.new( - params[:start_at], - params[:end_at], + def set_retention + @retention = Admin::Metrics::Retention.new( + params.require(:start_at), + params.require(:end_at), params[:frequency] - ).cohorts + ) end end diff --git a/app/lib/admin/metrics/dimension/base_dimension.rb b/app/lib/admin/metrics/dimension/base_dimension.rb index bd2e4ececbe..348d3cfc62f 100644 --- a/app/lib/admin/metrics/dimension/base_dimension.rb +++ b/app/lib/admin/metrics/dimension/base_dimension.rb @@ -17,6 +17,8 @@ class Admin::Metrics::Dimension::BaseDimension @limit = limit&.to_i @params = params @loaded = false + + @start_at = [@start_at, @end_at - 2.years].max if @start_at.present? && @end_at.present? end def key diff --git a/app/lib/admin/metrics/measure/base_measure.rb b/app/lib/admin/metrics/measure/base_measure.rb index 8b7fe39b55a..68714f6e2d3 100644 --- a/app/lib/admin/metrics/measure/base_measure.rb +++ b/app/lib/admin/metrics/measure/base_measure.rb @@ -12,10 +12,12 @@ class Admin::Metrics::Measure::BaseMeasure alias loaded? loaded def initialize(start_at, end_at, params) - @start_at = start_at&.to_datetime - @end_at = end_at&.to_datetime + @start_at = start_at.to_datetime + @end_at = end_at.to_datetime @params = params @loaded = false + + @start_at = [@start_at, @end_at - 2.years].max end def cache_key diff --git a/app/lib/admin/metrics/retention.rb b/app/lib/admin/metrics/retention.rb index e5ce5496098..64635eaf8ec 100644 --- a/app/lib/admin/metrics/retention.rb +++ b/app/lib/admin/metrics/retention.rb @@ -16,10 +16,13 @@ class Admin::Metrics::Retention alias loaded? loaded def initialize(start_at, end_at, frequency) - @start_at = start_at&.to_date - @end_at = end_at&.to_date + @start_at = start_at.to_date + @end_at = end_at.to_date + @frequency = %w(day month).include?(frequency) ? frequency : 'day' @loaded = false + + @start_at = [@start_at, @end_at - (@frequency == 'day' ? 31.days : 12.months)].max end def cache_key diff --git a/app/lib/private_address_check.rb b/app/lib/private_address_check.rb index e0ba017ba3c..8802d06e827 100644 --- a/app/lib/private_address_check.rb +++ b/app/lib/private_address_check.rb @@ -34,7 +34,7 @@ module PrivateAddressCheck module_function def private_address?(address) - address = address.native if address.ipv6? && address.ipv4_mapped? + address = address.native if address.ipv6? && (address.ipv4_mapped? || address.ipv4_compat?) address.private? || address.loopback? || address.link_local? || CIDR_LIST.any? { |cidr| cidr.include?(address) } end end diff --git a/docker-compose.yml b/docker-compose.yml index 3f603bc66c5..2d0b553143c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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/mastodon/mastodon:v4.4.20 + image: ghcr.io/mastodon/mastodon:v4.4.21 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/mastodon/mastodon-streaming:v4.4.20 + image: ghcr.io/mastodon/mastodon-streaming:v4.4.21 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/mastodon/mastodon:v4.4.20 + image: ghcr.io/mastodon/mastodon:v4.4.21 restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 5628f024b52..7f5525a9b6f 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 20 + 21 end def default_prerelease diff --git a/spec/requests/api/v1/admin/measures_spec.rb b/spec/requests/api/v1/admin/measures_spec.rb index b55cd0f1b20..021c4510fe1 100644 --- a/spec/requests/api/v1/admin/measures_spec.rb +++ b/spec/requests/api/v1/admin/measures_spec.rb @@ -22,6 +22,8 @@ RSpec.describe 'Admin Measures' do domain: 'mastodon.social', include_subdomains: true, }, + start_at: '2026-01-01', + end_at: '2026-07-01', } end diff --git a/spec/requests/api/v1/admin/retention_spec.rb b/spec/requests/api/v1/admin/retention_spec.rb index 25e626e2593..a832ba89561 100644 --- a/spec/requests/api/v1/admin/retention_spec.rb +++ b/spec/requests/api/v1/admin/retention_spec.rb @@ -11,7 +11,7 @@ RSpec.describe 'Admin Retention' do describe 'GET /api/v1/admin/retention' do context 'when not authorized' do it 'returns http forbidden' do - post '/api/v1/admin/retention', params: { account_id: account.id, limit: 2 } + post '/api/v1/admin/retention', params: { start_at: '2025-01-04', end_at: '2025-07-05', frequency: 'month' } expect(response) .to have_http_status(403) @@ -24,7 +24,7 @@ RSpec.describe 'Admin Retention' do let(:scopes) { 'admin:read' } it 'returns http success and status json' do - post '/api/v1/admin/retention', params: { account_id: account.id, limit: 2 }, headers: headers + post '/api/v1/admin/retention', params: { start_at: '2025-01-04', end_at: '2025-07-05', frequency: 'month' }, headers: headers expect(response) .to have_http_status(200)