CVE-2023-23622: Insufficient Information (NVD-CWE-noinfo)
value: Discourse is an open-source discussion platform. Prior to version 3.0.1 of the `stable` branch and version 3.1.0.beta2 of the `beta` and `tests-passed` branches, the count of topics displayed for a tag is a count of all regular topics regardless of whether the topic is in a read restricted category or not. As a result, any users can technically poll a sensitive tag to determine if a new topic is created in a category which the user does not have excess to. 

In version 3.0.1 of the `stable` branch and version 3.1.0.beta2 of the `beta` and `tests-passed` branches, the count of topics displayed for a tag defaults to only counting regular topics which are not in read restricted categories. Staff users will continue to see a count of all topics regardless of the topic's category read restrictions.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 4.3
Published: 2023-03-17T15:15Z
Repository: https://github.com/discourse/discourse
Language: Ruby
Files changed: app/controllers/tags_controller.rb, spec/requests/tags_controller_spec.rb
Commit date: 2023-01-25 18:55:55 +0200
Commit message: SECURITY: only show restricted tag lists to authorized users (#20004)

Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>
Diff: diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 1108b618cc74..aa5037e2ea1b 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -3,16 +3,17 @@
 class TagsController < ::ApplicationController
   include TopicListResponder
   include TopicQueryParams
 
   before_action :ensure_tags_enabled
-  before_action :ensure_visible, only: %i[show info]
 
   def self.show_methods
     Discourse.anonymous_filters.map { |f| :"show_#{f}" }
   end
 
+  before_action :ensure_visible, only: [:show, :info, *show_methods]
+
   requires_login except: [:index, :show, :tag_feed, :search, :info, *show_methods]
 
   skip_before_action :check_xhr, only: [:tag_feed, :show, :index, *show_methods]
 
   before_action :set_category,
diff --git a/spec/requests/tags_controller_spec.rb b/spec/requests/tags_controller_spec.rb
index 834589618be1..ac85694fc7cd 100644
--- a/spec/requests/tags_controller_spec.rb
+++ b/spec/requests/tags_controller_spec.rb
@@ -674,10 +674,22 @@ RSpec.describe TagsController do
           get "/tag/#{tag.name}/l/bookmarks.json"
 
           expect(response.status).to eq(200)
         end
 
+        it "returns a 404 when tag is restricted" do
+          tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])
+
+          get "/tag/test/l/latest.json"
+          expect(response.status).to eq(404)
+
+          sign_in(admin)
+
+          get "/tag/test/l/latest.json"
+          expect(response.status).to eq(200)
+        end
+
         context "with muted tags" do
           before do
             TagUser.create!(
               user_id: user.id,
               tag_id: tag.id,
@@ -753,10 +765,22 @@ RSpec.describe TagsController do
 
     it "raises an error if the period is not valid" do
       get "/tag/#{tag.name}/l/top.json?period=decadely"
       expect(response.status).to eq(400)
     end
+
+    it "returns a 404 if tag is restricted" do
+      tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])
+
+      get "/tag/test/l/top.json"
+      expect(response.status).to eq(404)
+
+      sign_in(admin)
+
+      get "/tag/test/l/top.json"
+      expect(response.status).to eq(200)
+    end
   end
 
   describe "#search" do
     context "with tagging disabled" do
       it "returns 404" do

Vulnerable code:   before_action :ensure_visible, only: %i[show info]
Fixed code:   before_action :ensure_visible, only: [:show, :info, *show_methods]

        it "returns a 404 when tag is restricted" do
          tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])

          get "/tag/test/l/latest.json"
          expect(response.status).to eq(404)

          sign_in(admin)

          get "/tag/test/l/latest.json"
          expect(response.status).to eq(200)
        end


    it "returns a 404 if tag is restricted" do
      tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])

      get "/tag/test/l/top.json"
      expect(response.status).to eq(404)

      sign_in(admin)

      get "/tag/test/l/top.json"
      expect(response.status).to eq(200)
    end