From f1f3dd8720566c0a613a38090af8333d6b01a20d Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 21 Aug 2026 14:31:52 +0200 Subject: [PATCH 1/7] Update security policy --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index 8e35943307..03affa5ade 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,6 +16,6 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through | Version | Supported | | ------- | ---------------- | | 4.6.0 | Yes | -| 4.5.x | Yes | +| 4.5.x | Until 2027-02-20 | | 4.4.x | Until 2026-12-17 | | < 4.4 | No | From 3ccfbbe195dcfc8edd0d24198d3f0e0bd06462dc Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 18 Aug 2026 13:19:35 +0000 Subject: [PATCH 2/7] Fix various off-by-one errors in statistics time ranges (#40193) Co-authored-by: Shlee --- app/controllers/admin/dashboard_controller.rb | 2 +- .../admin/instances/moderation_notes_controller.rb | 2 +- app/controllers/admin/instances_controller.rb | 2 +- app/controllers/admin/tags_controller.rb | 2 +- app/lib/activity_tracker.rb | 2 +- app/lib/admin/metrics/measure/base_measure.rb | 2 +- spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb | 6 ++++++ 7 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index fe314daeca..dc7908c725 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -12,7 +12,7 @@ module Admin @pending_tags_count = pending_tags.async_count @pending_users_count = User.pending.async_count @system_checks = Admin::SystemCheck.perform(current_user) - @time_period = (29.days.ago.to_date...Time.now.utc.to_date) + @time_period = (29.days.ago.to_date..Time.now.utc.to_date) end private diff --git a/app/controllers/admin/instances/moderation_notes_controller.rb b/app/controllers/admin/instances/moderation_notes_controller.rb index dd6c32bda5..d92e53c326 100644 --- a/app/controllers/admin/instances/moderation_notes_controller.rb +++ b/app/controllers/admin/instances/moderation_notes_controller.rb @@ -13,7 +13,7 @@ class Admin::Instances::ModerationNotesController < Admin::BaseController redirect_to admin_instance_path(@instance.domain, anchor: helpers.dom_id(@instance_moderation_note)), notice: I18n.t('admin.instances.moderation_notes.created_msg') else @instance_moderation_notes = @instance.moderation_notes.includes(:account).chronological - @time_period = (6.days.ago.to_date...Time.now.utc.to_date) + @time_period = (6.days.ago.to_date..Time.now.utc.to_date) @action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(5) render 'admin/instances/show' diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb index 033d250a2e..72024a7343 100644 --- a/app/controllers/admin/instances_controller.rb +++ b/app/controllers/admin/instances_controller.rb @@ -17,7 +17,7 @@ module Admin @instance_moderation_note = @instance.moderation_notes.new @instance_moderation_notes = @instance.moderation_notes.includes(:account).chronological - @time_period = (6.days.ago.to_date...Time.now.utc.to_date) + @time_period = (6.days.ago.to_date..Time.now.utc.to_date) @action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT) end diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb index f2c28328f8..2d6bfbe35c 100644 --- a/app/controllers/admin/tags_controller.rb +++ b/app/controllers/admin/tags_controller.rb @@ -38,7 +38,7 @@ module Admin end def report_range - (PERIOD_DAYS.ago.to_date...Time.now.utc.to_date) + (PERIOD_DAYS.ago.to_date..Time.now.utc.to_date) end def tag_params diff --git a/app/lib/activity_tracker.rb b/app/lib/activity_tracker.rb index d0e675f07d..bdb520d739 100644 --- a/app/lib/activity_tracker.rb +++ b/app/lib/activity_tracker.rb @@ -39,7 +39,7 @@ class ActivityTracker end def sum(start_at, end_at = Time.now.utc) - keys = (start_at.to_date...end_at.to_date).flat_map { |date| [key_at(date.to_time(:utc)), legacy_key_at(date)] }.uniq + keys = (start_at.to_date..end_at.to_date).flat_map { |date| [key_at(date.to_time(:utc)), legacy_key_at(date)] }.uniq case @type when :basic diff --git a/app/lib/admin/metrics/measure/base_measure.rb b/app/lib/admin/metrics/measure/base_measure.rb index 7a6bc21990..bf9816577c 100644 --- a/app/lib/admin/metrics/measure/base_measure.rb +++ b/app/lib/admin/metrics/measure/base_measure.rb @@ -97,7 +97,7 @@ class Admin::Metrics::Measure::BaseMeasure end def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) + ((@start_at.to_date - (length_of_period + 1))..(@end_at.to_date - (length_of_period + 1))) end def length_of_period diff --git a/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb b/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb index b258455dab..94d7054f47 100644 --- a/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb +++ b/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb @@ -17,6 +17,10 @@ RSpec.describe Admin::Metrics::Measure::TagUsesMeasure do let(:bob) { Fabricate(:account, domain: 'bob.example') } before do + 2.times do + travel_to(3.days.ago) { add_tag_history(alice) } + end + 3.times do travel_to(2.days.ago) { add_tag_history(alice) } end @@ -40,6 +44,8 @@ RSpec.describe Admin::Metrics::Measure::TagUsesMeasure do include(date: 1.day.ago.midnight.to_time, value: '4'), include(date: 0.days.ago.midnight.to_time, value: '1') ) + expect(subject.previous_total) + .to eq 2 end def add_tag_history(account) From a17c1b46588dbd8344940a416cb05e5b9c6b2d7c Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 21 Aug 2026 14:33:33 +0200 Subject: [PATCH 3/7] Fix error when processing remote actors with `null` public keys (#40194) --- app/services/activitypub/process_account_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 71d8e476e6..5cd3413e4f 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -298,6 +298,8 @@ class ActivityPub::ProcessAccountService < BaseService next unless value['owner'] == @account.uri key = value['publicKeyPem'] + next if key.nil? + { type: :rsa, public_key: key, uri: key_id } end end From f2ec372418c7bd8c6c1638ddc4544f9bd7fcc197 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 19 Aug 2026 12:01:43 +0000 Subject: [PATCH 4/7] Switch APT repos to canonical ubuntu repos rather than Azure mirror (#40205) --- .github/actions/setup-ruby/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/setup-ruby/action.yml b/.github/actions/setup-ruby/action.yml index f26448f9ed..8dcdafb7d9 100644 --- a/.github/actions/setup-ruby/action.yml +++ b/.github/actions/setup-ruby/action.yml @@ -13,6 +13,7 @@ runs: - name: Install system dependencies shell: bash run: | + sudo sed -i s:azure\.archive\.ubuntu\.com:archive.ubuntu.com:g /etc/apt/apt-mirrors.txt sudo apt-get update sudo apt-get install --no-install-recommends -y \ libicu-dev \ From d842aa256619e347d024961b717aa1c85c6b51d1 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 20 Aug 2026 08:14:50 +0000 Subject: [PATCH 5/7] Fix autosuggestions overstaying their welcome in languages that don't use spaces (#40217) --- .../components/autosuggest/utils.test.ts | 38 ++++++++++++++++++- .../mastodon/components/autosuggest/utils.ts | 2 +- app/javascript/mastodon/utils/hashtags.ts | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/components/autosuggest/utils.test.ts b/app/javascript/mastodon/components/autosuggest/utils.test.ts index 79b8b767b8..71b8fbdb1b 100644 --- a/app/javascript/mastodon/components/autosuggest/utils.test.ts +++ b/app/javascript/mastodon/components/autosuggest/utils.test.ts @@ -6,6 +6,14 @@ describe('textAtCursorMatchesToken', () => { ['#hashtag', 7, ['#']], [1, '#hashtag'], ], + [ + ['@alice', 6, ['@']], + [1, '@alice'], + ], + [ + ['@alice', 6, ['@']], + [1, '@alice'], + ], [ ['#hash tag', 8, ['#']], [1, '#hash tag'], @@ -24,7 +32,35 @@ describe('textAtCursorMatchesToken', () => { ], [ ['#ハッシュ タグ', 7, ['#']], - [1, '#ハッシュ タグ'], + [null, null], + ], + [ + ['@alice reply', 12, ['@']], + [1, '@alice reply'], + ], + [ + ['@alice 这是我输入的回复内容', 17, ['@']], + [null, null], + ], + [ + ['@alice これは本文', 12, ['@']], + [null, null], + ], + [ + ['@alice 이것은답변입니다', 15, ['@']], + [null, null], + ], + [ + ['@alice これは本文', 12, ['@']], + [null, null], + ], + [ + ['@алиса', 6, ['@']], + [1, '@алиса'], + ], + [ + ['@алиса пример', 13, ['@']], + [1, '@алиса пример'], ], ] as const)( 'textAtCursorMatchesToken(%s) is %o', diff --git a/app/javascript/mastodon/components/autosuggest/utils.ts b/app/javascript/mastodon/components/autosuggest/utils.ts index 3e02114a97..f41668fcf8 100644 --- a/app/javascript/mastodon/components/autosuggest/utils.ts +++ b/app/javascript/mastodon/components/autosuggest/utils.ts @@ -8,7 +8,7 @@ export const textAtCursorMatchesToken = ( let word: string; const regex = new RegExp( - `[${searchTokens.join('')}${WORD}+-]+(\\s[${WORD}]+)?$`, + `[${searchTokens.join('')}${WORD}+-]+(\\s[\\p{Script=Latin}\\p{Script=Cyrillic}\\p{M}]+)?$`, 'iu', ); const left = str.slice(0, caretPosition).search(regex); diff --git a/app/javascript/mastodon/utils/hashtags.ts b/app/javascript/mastodon/utils/hashtags.ts index d136f1ff60..5ec215aa3f 100644 --- a/app/javascript/mastodon/utils/hashtags.ts +++ b/app/javascript/mastodon/utils/hashtags.ts @@ -1,5 +1,5 @@ const HASHTAG_SEPARATORS = '_\\u00b7\\u200c'; -const ALPHA = '\\p{L}\\p{M}'; +export const ALPHA = '\\p{L}\\p{M}'; export const WORD = '\\p{L}\\p{M}\\p{N}\\p{Pc}'; const buildHashtagPatternRegex = () => { From b7d841cdd8d3c5138e6cce097afc00502b7bded4 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 24 Aug 2026 08:55:55 +0000 Subject: [PATCH 6/7] Add the `config/` directory to Bootsnap precompilation in Dockerfile (#40255) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c95f356937..d850abb5ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -399,7 +399,7 @@ RUN \ RUN \ # Precompile bootsnap code for faster Rails startup - bundle exec bootsnap precompile --gemfile app/ lib/; + bundle exec bootsnap precompile --gemfile app/ lib/ config/; RUN \ # Pre-create and chown system volume to Mastodon user From 70ca3d5ee7042810636f7270ce942ad176a98209 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 28 Aug 2026 11:49:30 +0000 Subject: [PATCH 7/7] Add some spec coverage (#40306) --- spec/lib/private_address_check_spec.rb | 2 +- .../process_collection_service_spec.rb | 40 +++++++++++++++++++ .../email_address_validator_spec.rb | 25 ++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 spec/validators/email_address_validator_spec.rb diff --git a/spec/lib/private_address_check_spec.rb b/spec/lib/private_address_check_spec.rb index 20a94983d9..0c02055187 100644 --- a/spec/lib/private_address_check_spec.rb +++ b/spec/lib/private_address_check_spec.rb @@ -4,7 +4,7 @@ require 'rails_helper' RSpec.describe PrivateAddressCheck do describe 'private_address?' do - let(:private_ips) { %w(192.168.1.7 0.0.0.0 127.0.0.1 ::ffff:0.0.0.1) } + let(:private_ips) { %w(192.168.1.7 0.0.0.0 127.0.0.1 ::ffff:0.0.0.1 ::127.0.0.1 ::ffff:127.0.0.1 ::ffff:10.0.0.1 ::ffff:169.254.169.254 ::) } it 'returns true for private addresses' do expect(private_ips) diff --git a/spec/services/activitypub/process_collection_service_spec.rb b/spec/services/activitypub/process_collection_service_spec.rb index c5fa38c641..96bf6124e1 100644 --- a/spec/services/activitypub/process_collection_service_spec.rb +++ b/spec/services/activitypub/process_collection_service_spec.rb @@ -109,6 +109,46 @@ RSpec.describe ActivityPub::ProcessCollectionService do expect(ActivityPub::Activity).to_not have_received(:factory) end + context 'when receiving a status with an unsupported JSON-LD keyword' do + let(:payload) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'foo', + type: 'Announce', + actor: ActivityPub::TagManager.instance.uri_for(actor), + '@reverse': { + object: { + type: 'Undo', + id: 'bar', + actor: ActivityPub::TagManager.instance.uri_for(actor), + }, + }, + object: { + id: 'bar', + type: 'Note', + content: 'Lorem ipsum', + }, + signature: { + type: 'RsaSignature2017', + creator: "#{ActivityPub::TagManager.instance.uri_for(actor)}#foo", + created: '2022-03-09T21:57:25Z', + signatureValue: 'foo', + }, + } + end + + it 'does not process payload' do + signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: nil) + allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double) + allow(ActivityPub::Activity).to receive(:factory) + + subject.call(json, forwarder) + + expect(signature_double).to_not have_received(:verify_actor!) + expect(ActivityPub::Activity).to_not have_received(:factory) + end + end + context 'when receiving a fabricated status' do let!(:actor) do Fabricate(:account, diff --git a/spec/validators/email_address_validator_spec.rb b/spec/validators/email_address_validator_spec.rb new file mode 100644 index 0000000000..1739826909 --- /dev/null +++ b/spec/validators/email_address_validator_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe EmailAddressValidator do + subject { record_class.new } + + context 'with no options' do + let(:record_class) do + Class.new do + include ActiveModel::Validations + + def self.name = 'Record' + + attr_accessor :email + + validates :email, email_address: true + end + end + + it { is_expected.to allow_value('foo@example.com').for(:email) } + it { is_expected.to_not allow_value('foo @ example.com').for(:email) } + it { is_expected.to_not allow_value('foo@example.com%foo.example.com').for(:email) } + end +end