CVE-2022-46150: Exposure of Sensitive Information to an Unauthorized Actor (CWE-200)
value: Discourse is an open-source discussion platform. Prior to version 2.8.13 of the `stable` branch and version 2.9.0.beta14 of the `beta` and `tests-passed` branches, unauthorized users may learn of the existence of hidden tags and that they have been applied to topics that they have access to. This issue is patched in version 2.8.13 of the `stable` branch and version 2.9.0.beta14 of the `beta` and `tests-passed` branches. As a workaround, use the `disable_email` site setting to disable all emails to non-staff users.
CWE description: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
CVSS v3 base score: 4.3
Published: 2022-11-29T18:15Z
Repository: https://github.com/discourse/discourse
Language: Ruby
Files changed: app/mailers/user_notifications.rb, spec/mailers/user_notifications_spec.rb
Commit date: 2022-11-29 10:35:41 -0600
Commit message: SECURITY: Filter tags in user notifications for visibility (#19239)


Diff: diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb
index 0c36b6aeea8e..78bdbd722b23 100644
--- a/app/mailers/user_notifications.rb
+++ b/app/mailers/user_notifications.rb
@@ -531,15 +531,17 @@ class UserNotifications < ActionMailer::Base
       show_category_in_subject = nil
     end
 
     # tag names
     if opts[:show_tags_in_subject] && post.topic_id
-
-      tags = Tag.joins(:topic_tags)
-        .where("topic_tags.topic_id = ?", post.topic_id)
-        .limit(3)
-        .pluck(:name)
+      tags =
+        DiscourseTagging
+          .visible_tags(Guardian.new(user))
+          .joins(:topic_tags)
+          .where("topic_tags.topic_id = ?", post.topic_id)
+          .limit(3)
+          .pluck(:name)
 
       show_tags_in_subject = tags.any? ? tags.join(" ") : nil
     end
 
     group = post.topic.allowed_groups&.first
diff --git a/spec/mailers/user_notifications_spec.rb b/spec/mailers/user_notifications_spec.rb
index 1a7c28097fdf..63e4350633ad 100644
--- a/spec/mailers/user_notifications_spec.rb
+++ b/spec/mailers/user_notifications_spec.rb
@@ -296,11 +296,17 @@ RSpec.describe UserNotifications do
   describe '.user_replied' do
     let(:response_by_user) { Fabricate(:user, name: "John Doe") }
     let(:category) { Fabricate(:category, name: 'India') }
     let(:tag1) { Fabricate(:tag, name: 'Taggo') }
     let(:tag2) { Fabricate(:tag, name: 'Taggie') }
-    let(:topic) { Fabricate(:topic, category: category, tags: [tag1, tag2], title: "Super cool topic") }
+
+    let(:hidden_tag) { Fabricate(:tag, name: "hidden") }
+    let!(:hidden_tag_group) do
+      Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name])
+    end
+
+    let(:topic) { Fabricate(:topic, category: category, tags: [tag1, tag2, hidden_tag], title: "Super cool topic") }
     let(:post) { Fabricate(:post, topic: topic, raw: 'This is My super duper cool topic') }
     let(:response) { Fabricate(:basic_reply, topic: post.topic, user: response_by_user) }
     let(:user) { Fabricate(:user) }
     let(:notification) { Fabricate(:replied_notification, user: user, post: response) }
 
@@ -375,10 +381,13 @@ RSpec.describe UserNotifications do
 
       mail_html = mail.html_part.body.to_s
       expect(mail_html.scan(/>Bob Marley/).count).to eq(1)
       expect(mail_html.scan(/>bobmarley/).count).to eq(0)
 
+      expect(mail.subject.scan(/#{tag1.name}/).count).to eq(1)
+      expect(mail.subject.scan(/#{hidden_tag.name}/).count).to eq(0)
+
       SiteSetting.prioritize_username_in_ux = true
 
       mail = UserNotifications.user_replied(
         user,
         post: response,

Vulnerable code: 
      tags = Tag.joins(:topic_tags)
        .where("topic_tags.topic_id = ?", post.topic_id)
        .limit(3)
        .pluck(:name)
    let(:topic) { Fabricate(:topic, category: category, tags: [tag1, tag2], title: "Super cool topic") }
Fixed code:       tags =
        DiscourseTagging
          .visible_tags(Guardian.new(user))
          .joins(:topic_tags)
          .where("topic_tags.topic_id = ?", post.topic_id)
          .limit(3)
          .pluck(:name)

    let(:hidden_tag) { Fabricate(:tag, name: "hidden") }
    let!(:hidden_tag_group) do
      Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name])
    end

    let(:topic) { Fabricate(:topic, category: category, tags: [tag1, tag2, hidden_tag], title: "Super cool topic") }
      expect(mail.subject.scan(/#{tag1.name}/).count).to eq(1)
      expect(mail.subject.scan(/#{hidden_tag.name}/).count).to eq(0)