diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c57762eb5190..3a77d70a7721b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to this project will be documented in this file. +## [4.5.9] - 2026-04-15 + +### Security + +- Insufficient verification of email addresses ([GHSA-5r37-qpwq-2jhh](https://github.com/mastodon/mastodon/security/advisories/GHSA-5r37-qpwq-2jhh)) +- Updated dependencies + +### Added + +- Add trademark warning to `mastodon:setup` task (#38548 by @ClearlyClaire) + +### Fixed + +- Fix definition for `quote` in JSON-LD context (#38686 by @ClearlyClaire) +- Fix being unable to disable sound for quote update notification (#38537 by @ClearlyClaire) +- Fix being able to quote someone you blocked (#38608 by @ClearlyClaire) + ## [4.5.8] - 2026-03-24 ### Security diff --git a/Gemfile b/Gemfile index ff3097e6c2f5e9..8aa1c297429023 100644 --- a/Gemfile +++ b/Gemfile @@ -13,7 +13,7 @@ gem 'haml-rails', '~>3.0' gem 'pg', '~> 1.5' gem 'pghero' -gem 'aws-sdk-core', '< 3.216.0', require: false # TODO: https://github.com/mastodon/mastodon/pull/34173#issuecomment-2733378873 +gem 'aws-sdk-core', require: false gem 'aws-sdk-s3', '~> 1.123', require: false gem 'blurhash', '~> 0.1' gem 'fog-core', '<= 2.6.0' diff --git a/Gemfile.lock b/Gemfile.lock index 718bfadf5d39ff..95ee2c82949b35 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -96,17 +96,20 @@ GEM ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.4.0) - aws-partitions (1.1168.0) - aws-sdk-core (3.215.1) + aws-partitions (1.1238.0) + aws-sdk-core (3.244.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) + base64 + bigdecimal jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.96.0) - aws-sdk-core (~> 3, >= 3.210.0) + logger + aws-sdk-kms (1.123.0) + aws-sdk-core (~> 3, >= 3.244.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.177.0) - aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-s3 (1.219.0) + aws-sdk-core (~> 3, >= 3.244.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) @@ -115,7 +118,7 @@ GEM rexml base64 (0.3.0) bcp47_spec (0.2.1) - bcrypt (3.1.20) + bcrypt (3.1.22) benchmark (0.5.0) better_errors (2.10.1) erubi (>= 1.0.0) @@ -346,7 +349,7 @@ GEM azure-blob (~> 0.5.2) hashie (~> 5.0) jmespath (1.6.2) - json (2.15.1) + json (2.15.2.1) json-canonicalization (1.0.0) json-jwt (1.17.0) activesupport (>= 4.2) @@ -621,7 +624,7 @@ GEM activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) - rack (3.2.5) + rack (3.2.6) rack-attack (6.8.0) rack (>= 1.0, < 4) rack-cors (3.0.0) @@ -640,7 +643,7 @@ GEM rack (>= 3.0.0, < 4) rack-proxy (0.7.7) rack - rack-session (2.1.1) + rack-session (2.1.2) base64 (>= 0.1.0) rack (>= 3.0.0) rack-test (2.2.0) @@ -933,7 +936,7 @@ DEPENDENCIES active_model_serializers (~> 0.10) addressable (~> 2.8) annotaterb (~> 4.13) - aws-sdk-core (< 3.216.0) + aws-sdk-core aws-sdk-s3 (~> 1.123) better_errors (~> 2.9) binding_of_caller (~> 1.0) diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 74702d20890451..659a30256c9a8b 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -38,7 +38,7 @@ module ContextHelper misskey_license: { 'misskey' => 'https://misskey-hub.net/ns#', '_misskey_license' => 'misskey:_misskey_license' }, quote_requests: { 'QuoteRequest' => 'https://w3id.org/fep/044f#QuoteRequest' }, quotes: { - 'quote' => 'https://w3id.org/fep/044f#quote', + 'quote' => { '@id' => 'https://w3id.org/fep/044f#quote', '@type' => '@id' }, 'quoteUri' => 'http://fedibird.com/ns#quoteUri', '_misskey_quote' => 'https://misskey-hub.net/ns#_misskey_quote', 'quoteAuthorization' => { '@id' => 'https://w3id.org/fep/044f#quoteAuthorization', '@type' => '@id' }, diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 802d55c43cf265..78844594426683 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -47,8 +47,10 @@ export function updateEmojiReactions(emoji_reaction) { export function updateNotifications(notification, intlMessages, intlLocale) { return (dispatch, getState) => { - const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); - const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); + const filterType = notification.type === 'quoted_update' ? 'update' : notification.type; + + const showAlert = getState().getIn(['settings', 'notifications', 'alerts', filterType], true); + const playSound = getState().getIn(['settings', 'notifications', 'sounds', filterType], true); let filtered = false; diff --git a/app/models/user.rb b/app/models/user.rb index 3d21805a22efc0..50e84fe3a6aaf9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -97,7 +97,7 @@ class User < ApplicationRecord has_one :custom_css, inverse_of: :user, dependent: :destroy - validates :email, presence: true, email_address: true + validates :email, presence: true, email_address: true, length: { maximum: 320 } validates_with UserEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? } validates_with EmailMxValidator, if: :validate_email_dns? diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 1ae1c4731f91c4..38e1facfe548e0 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -35,9 +35,8 @@ def show_activity? following_author_domain? end - # This is about requesting a quote post, not validating it def quote? - show? && record.quote_policy_for_account(current_account, preloaded_relations: @preloaded_relations) != :denied + show? && !blocking_author? && record.quote_policy_for_account(current_account, preloaded_relations: @preloaded_relations) != :denied end def reblog? diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index cc89a417a942b4..8c4ce25b588c63 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -32,8 +32,15 @@ def call(username, domain, json, options = {}) @options[:request_id] ||= "#{Time.now.utc.to_i}-#{username}@#{domain}" with_redis_lock("process_account:#{@uri}") do - @account = Account.remote.find_by(uri: @uri) if @options[:only_key] - @account ||= Account.find_remote(@username, @domain) + if @options[:only_key] + # `only_key` is used to update an existing account known by its `uri`. + # Lookup by handle and new account creation do not make sense in this case. + @account = Account.remote.find_by(uri: @uri) + return if @account.nil? + else + @account = Account.find_remote(@username, @domain) + end + @old_public_key = @account&.public_key @old_protocol = @account&.protocol @old_searchability = @account&.searchability diff --git a/app/validators/email_address_validator.rb b/app/validators/email_address_validator.rb index ed0bb116524aec..7cc303a6369ead 100644 --- a/app/validators/email_address_validator.rb +++ b/app/validators/email_address_validator.rb @@ -11,8 +11,14 @@ def validate_each(record, attribute, value) value = value.strip address = Mail::Address.new(value) - record.errors.add(attribute, :invalid) if address.address != value + record.errors.add(attribute, :invalid) if address.address != value || contains_disallowed_characters?(value) rescue Mail::Field::FieldError record.errors.add(attribute, :invalid) end + + private + + def contains_disallowed_characters?(value) + value.include?('%') || value.include?(',') || value.include?('"') + end end diff --git a/docker-compose.yml b/docker-compose.yml index f14e4d9fbf89f0..d80c30cb64e705 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: kmyblue:21.9-lts + image: kmyblue:21.10-lts 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: kmyblue-streaming:21.9-lts + image: kmyblue-streaming:21.10-lts restart: always env_file: .env.production command: node ./streaming/index.js @@ -101,7 +101,7 @@ services: sidekiq: build: . - image: kmyblue:21.9-lts + image: kmyblue:21.10-lts restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index ce88f6cb8a50fa..30d42bde9f5169 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ def kmyblue_major end def kmyblue_minor - 9 + 10 end def kmyblue_flag @@ -31,7 +31,7 @@ def minor end def patch - 8 + 9 end def default_prerelease diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 66f6245d6dc48e..79902b2029a0a3 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -27,6 +27,12 @@ namespace :mastodon do q.messages[:valid?] = 'Invalid domain. If you intend to use unicode characters, enter punycode here' end + if env['LOCAL_DOMAIN'].include?('mastodon') || env['LOCAL_DOMAIN'].include?('mstdn') + prompt.warn 'The Mastodon name is a trademark and its use is restricted.' + prompt.warn 'You can read the trademark policy at https://joinmastodon.org/trademark' + next prompt.warn 'Nothing saved. Bye!' if prompt.no?('Continue anyway?') + end + prompt.say "\n" prompt.say('Single user mode disables registrations and redirects the landing page to your public profile.') diff --git a/package.json b/package.json index 0436668f20b58c..6a6a8396712384 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@vitejs/plugin-react": "^5.0.0", "arrow-key-navigation": "^1.2.0", "async-mutex": "^0.5.0", - "axios": "^1.4.0", + "axios": "^1.15.0", "babel-plugin-formatjs": "^10.5.37", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "blurhash": "^2.0.5", @@ -83,11 +83,11 @@ "hoist-non-react-statics": "^3.3.2", "http-link-header": "^1.1.1", "idb": "^8.0.3", - "immutable": "^4.3.0", + "immutable": "^4.3.7", "intl-messageformat": "^10.7.16", "js-yaml": "^4.1.0", "lande": "^1.0.10", - "lodash": "^4.17.21", + "lodash": "4.18.1", "marky": "^1.2.5", "path-complete-extname": "^1.0.0", "postcss-preset-env": "^10.1.5", diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index 82715f328214a6..ebcbc396594aa7 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -393,6 +393,56 @@ end end + context 'with a quote in an unlisted message' do + let!(:quoted_status) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) } + let(:params) do + { + status: 'Hello, this is a quote', + quoted_status_id: quoted_status.id, + visibility: 'unlisted', + } + end + + it 'returns a quote post, as well as rate limit headers', :aggregate_failures do + expect { subject }.to change(user.account.statuses, :count).by(1) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:quote]).to be_present + expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s + expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s + end + + context 'when the quoter is blocked by the quotee' do + before do + quoted_status.account.block!(user.account) + end + + it 'returns an error and does not create a post', :aggregate_failures do + expect { subject }.to_not change(user.account.statuses, :count) + + expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'when the quotee is blocked by the quoter' do + before do + user.account.block!(quoted_status.account) + end + + it 'returns an error and does not create a post', :aggregate_failures do + expect { subject }.to_not change(user.account.statuses, :count) + + expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') + end + end + end + context 'with a quote of a reblog' do let(:quoted_status) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) } let(:reblog) { Fabricate(:status, reblog: quoted_status) } diff --git a/yarn.lock b/yarn.lock index d129b86d7754b3..742fe275512f37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2857,7 +2857,7 @@ __metadata: "@vitest/ui": "npm:^3.2.4" arrow-key-navigation: "npm:^1.2.0" async-mutex: "npm:^0.5.0" - axios: "npm:^1.4.0" + axios: "npm:^1.15.0" babel-plugin-formatjs: "npm:^10.5.37" babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24" blurhash: "npm:^2.0.5" @@ -2893,12 +2893,12 @@ __metadata: http-link-header: "npm:^1.1.1" husky: "npm:^9.0.11" idb: "npm:^8.0.3" - immutable: "npm:^4.3.0" + immutable: "npm:^4.3.7" intl-messageformat: "npm:^10.7.16" js-yaml: "npm:^4.1.0" lande: "npm:^1.0.10" lint-staged: "npm:^16.0.0" - lodash: "npm:^4.17.21" + lodash: "npm:4.18.1" marky: "npm:^1.2.5" msw: "npm:^2.10.2" msw-storybook-addon: "npm:^2.0.5" @@ -5492,14 +5492,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.4.0": - version: 1.13.1 - resolution: "axios@npm:1.13.1" +"axios@npm:^1.15.0": + version: 1.15.0 + resolution: "axios@npm:1.15.0" dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.4" - proxy-from-env: "npm:^1.1.0" - checksum: 10c0/de9c3c6de43d3ee1146d3afe78645f19450cac6a5d7235bef8b8e8eeb705c2e47e2d231dea99cecaec4dae1897c521118ca9413b9d474063c719c4d94c5b9adc + follow-redirects: "npm:^1.15.11" + form-data: "npm:^4.0.5" + proxy-from-env: "npm:^2.1.0" + checksum: 10c0/47e0f860e98d4d7aa145e89ce0cae00e1fb0f1d2485f065c21fce955ddb1dba4103a46bd0e47acd18a27208a7f62c96249e620db575521b92a968619ab133409 languageName: node linkType: hard @@ -7700,13 +7700,13 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.15.6": - version: 1.15.6 - resolution: "follow-redirects@npm:1.15.6" +"follow-redirects@npm:^1.15.11": + version: 1.16.0 + resolution: "follow-redirects@npm:1.16.0" peerDependenciesMeta: debug: optional: true - checksum: 10c0/9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 + checksum: 10c0/a1e2900163e6f1b4d1ed5c221b607f41decbab65534c63fe7e287e40a5d552a6496e7d9d7d976fa4ba77b4c51c11e5e9f683f10b43011ea11e442ff128d0e181 languageName: node linkType: hard @@ -7729,16 +7729,16 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.4": - version: 4.0.4 - resolution: "form-data@npm:4.0.4" +"form-data@npm:^4.0.5": + version: 4.0.5 + resolution: "form-data@npm:4.0.5" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" es-set-tostringtag: "npm:^2.1.0" hasown: "npm:^2.0.2" mime-types: "npm:^2.1.12" - checksum: 10c0/373525a9a034b9d57073e55eab79e501a714ffac02e7a9b01be1c820780652b16e4101819785e1e18f8d98f0aee866cc654d660a435c378e16a72f2e7cac9695 + checksum: 10c0/dd6b767ee0bbd6d84039db12a0fa5a2028160ffbfaba1800695713b46ae974a5f6e08b3356c3195137f8530dcd9dfcb5d5ae1eeff53d0db1e5aad863b619ce3b languageName: node linkType: hard @@ -8360,10 +8360,10 @@ __metadata: languageName: node linkType: hard -"immutable@npm:^4.0.0-rc.1, immutable@npm:^4.3.0": - version: 4.3.7 - resolution: "immutable@npm:4.3.7" - checksum: 10c0/9b099197081b22f6433003e34929da8ecddbbdc1474cdc8aa3b7669dee4adda349c06143de22def36016d1b6de5322b043eccd7a11db1dad2ca85dad4fff5435 +"immutable@npm:^4.0.0-rc.1, immutable@npm:^4.3.7": + version: 4.3.8 + resolution: "immutable@npm:4.3.8" + checksum: 10c0/3de58996305a0faf6ef3fc0685f996c42653ad757760214f5aec7d4a6b59ea7abb882522c5f9a61776fae88c0b45e08eb77cbded5a4f57745ec7c63f9642e44b languageName: node linkType: hard @@ -9375,10 +9375,10 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.20, lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c +"lodash@npm:4.18.1, lodash@npm:^4.17.20, lodash@npm:^4.17.21": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 languageName: node linkType: hard @@ -11218,10 +11218,10 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:^1.1.0": - version: 1.1.0 - resolution: "proxy-from-env@npm:1.1.0" - checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 languageName: node linkType: hard