CVE-2022-41944 (Ruby, published 2022-11-28T15:15Z)
Description: value: Discourse is an open-source discussion platform. In stable versions prior to 2.8.12 and beta or tests-passed versions prior to 2.9.0.beta.13, under certain conditions, a user can see notifications for topics they no longer have access to. If there is sensitive information in the topic title, it will therefore have been exposed. This issue is patched in stable version 2.8.12, beta version 2.9.0.beta13, and tests-passed version 2.9.0.beta13. There are no workarounds available.
Severity: nan (CVSS v3 base score: 4.3)
Weakness: CWE-863 \u2014 Incorrect Authorization
The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
Repository: https://github.com/discourse/discourse
Files changed:
app/controllers/notifications_controller.rb
spec/requests/notifications_controller_spec.rb
Commit message: SECURITY: Hide notifications for inaccessible topics (#19208)

Filter notifications the user cannot see anymore
via guardian.can_see_topic_ids

Vulnerable code:
        if changed = current_user.bump_last_seen_notification!

Fixed code:
        if current_user.bump_last_seen_notification!
      notifications = filter_inaccessible_notifications(notifications)

      notifications = filter_inaccessible_notifications(notifications)
  def filter_inaccessible_notifications(notifications)
    topic_ids = notifications.map { |n| n.topic_id }.compact.uniq
    accessible_topic_ids = guardian.can_see_topic_ids(topic_ids: topic_ids)
    notifications.select { |n| n.topic_id.blank? || accessible_topic_ids.include?(n.topic_id) }
  end

        context "with notifications for inaccessible topics" do
          fab!(:sender) { Fabricate.build(:topic_allowed_user, user: Fabricate(:coding_horror)) }
          fab!(:allowed_user) { Fabricate.build(:topic_allowed_user, user: user) }
          fab!(:another_allowed_user) { Fabricate.build(:topic_allowed_user, user: Fabricate(:user)) }
          fab!(:allowed_pm) { Fabricate(:private_message_topic, topic_allowed_users: [sender, allowed_user, another_allowed_user]) }
          fab!(:forbidden_pm) { Fabricate(:private_message_topic, topic_allowed_users: [sender, another_allowed_user]) }
          fab!(:allowed_pm_notification) { Fabricate(:private_message_notification, user: user, topic: allowed_pm) }
          fab!(:forbidden_pm_notification) { Fabricate(:private_message_notification, user: user, topic: forbidden_pm) }

          def expect_correct_notifications(response)
            notification_ids = response.parsed_body["notifications"].map { |n| n["id"] }
            expect(notification_ids).to include(allowed_pm_notification.id)
            expect(notification_ids).to_not include(forbidden_pm_notification.id)
          end

          context "with 'recent' filter" do
            it "doesn't include notifications from topics the user isn't allowed to see" do
              SiteSetting.enable_experimental_sidebar_hamburger = true
              get "/notifications.json", params: { recent: true }
              expect(response.status).to eq(200)
              expect_correct_notifications(response)

              SiteSetting.enable_experimental_sidebar_hamburger = false
              get "/notifications.json", params: { recent: true }
              expect(response.status).to eq(200)
              expect_correct_notifications(response)
            end
          end

          context "without 'recent' filter" do
            it "doesn't include notifications from topics the user isn't allowed to see" do
              SiteSetting.enable_experimental_sidebar_hamburger = true
              get "/notifications.json"
              expect(response.status).to eq(200)
              expect_correct_notifications(response)

              SiteSetting.enable_experimental_sidebar_hamburger = false
              get "/notifications.json"
              expect(response.status).to eq(200)
              expect_correct_notifications(response)
            end
          end
        end