CVE-2023-25819 (Ruby, published 2023-03-04T01:15Z)
Description: value: Discourse is an open source platform for community discussion. Tags that are normally private are showing in metadata. This affects any site running the `tests-passed` or `beta` branches >= 3.1.0.beta2. The issue is patched in the latest `beta` and `tests-passed` version of Discourse.
Severity: nan (CVSS v3 base score: 5.3)
Weakness: CWE-200 \u2014 Exposure of Sensitive Information to an Unauthorized Actor
The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
Repository: https://github.com/discourse/discourse
Files changed:
app/controllers/topics_controller.rb
app/models/tag.rb
app/views/topics/show.html.erb
lib/topic_view.rb
spec/lib/topic_view_spec.rb
spec/requests/topics_controller_spec.rb
spec/views/topics/show.html.erb_spec.rb
Commit message: SECURITY: Show only visible tags in metadata

Currently, the topic metadata show both public and private
tags whereas only visible ones should be exposed.


Vulnerable code:
        @tags = SiteSetting.tagging_enabled ? @topic_view.topic.tags : []
    <%= raw crawlable_meta_data(title: @topic_view.title, description: @topic_view.summary(strip_images: true), image: @topic_view.image_url, read_time: @topic_view.read_time, like_count: @topic_view.like_count, ignore_canonical: true, published_time: @topic_view.published_time, breadcrumbs: @breadcrumbs, tags: @topic_view.tags) %>
      elsif SiteSetting.tagging_enabled && @topic.tags.exists?
          " - #{@topic.tags.order("tags.#{Tag.topic_count_column(@guardian)} DESC").first.name}"
  def tags
    @topic.tags.map(&:name)
  end

    Topic.with_deleted.includes(:category, :tags).find_by(id: topic_or_topic_id)

  describe "#tags" do
    subject(:topic_view_tags) { topic_view.tags }

    let(:topic_view) { described_class.new(topic, user) }
    let(:topic) { Fabricate.build(:topic, tags: tags) }
    let(:tags) { Fabricate.build_times(2, :tag) }
    let(:user) { Fabricate(:user) }

    it "returns the tags names" do
      expect(topic_view_tags).to match tags.map(&:name)
    end
  end

Fixed code:
        @tags = SiteSetting.tagging_enabled ? @topic_view.topic.tags.visible(guardian) : []
  scope :visible, ->(guardian = nil) { merge(DiscourseTagging.visible_tags(guardian)) }
    <%= raw crawlable_meta_data(title: @topic_view.title, description: @topic_view.summary(strip_images: true), image: @topic_view.image_url, read_time: @topic_view.read_time, like_count: @topic_view.like_count, ignore_canonical: true, published_time: @topic_view.published_time, breadcrumbs: @breadcrumbs, tags: @tags.map(&:name)) %>
      elsif SiteSetting.tagging_enabled && visible_tags.exists?
          " - #{visible_tags.order("tags.#{Tag.topic_count_column(@guardian)} DESC").first.name}"
    Topic.with_deleted.includes(:category).find_by(id: topic_or_topic_id)

  def visible_tags
    @visible_tags ||= topic.tags.visible(guardian)
  end

          context "with restricted tags" do
            let(:tag_group) { Fabricate.build(:tag_group) }
            let(:tag_group_permission) do
              Fabricate.build(:tag_group_permission, tag_group: tag_group)
            end

            before do
              SiteSetting.tagging_enabled = true
              # avoid triggering a `before_create` callback in `TagGroup` which
              # messes with permissions
              tag_group.tag_group_permissions << tag_group_permission
              tag_group.save!
              tag_group_permission.tag_group.tags << tag2
            end

            it { is_expected.not_to include(tag2.name) }
            it { is_expected.to include(tag1.name) }
          end

      context "with restricted tags" do
        let(:tag_group) { Fabricate.build(:tag_group) }
        let(:tag_group_permission) { Fabricate.build(:tag_group_permission, tag_group: tag_group) }
        let(:restricted_tag) { Fabricate(:tag) }
        let(:public_tag) { Fabricate(:tag) }

        before do
          # avoid triggering a `before_create` callback in `TagGroup` which
          # messes with permissions
          tag_group.tag_group_permissions << tag_group_permission
          tag_group.save!
          tag_group_permission.tag_group.tags << restricted_tag
          topic.tags << [public_tag, restricted_tag]
        end

        it "doesn’t expose restricted tags" do
          get "/t/#{topic.slug}/#{topic.id}/print", headers: { HTTP_USER_AGENT: "Rails Testing" }
          expect(response.body).to match(public_tag.name)
          expect(response.body).not_to match(restricted_tag.name)
        end
      end
    assign(:tags, [])