VYPR
Medium severityNVD Advisory· Published May 19, 2026· Updated May 19, 2026

CVE-2026-33514

CVE-2026-33514

Description

Discourse is an open-source discussion platform. In versions prior to 2026.1.4, 2026.3.1, 2026.4.1 and 2026.5.0-latest.1, an authenticated user on a Discourse instance with the form templates feature enabled can read the name and structured content of form templates that are intended exclusively for categories they are not authorized to access. Impact is limited to disclosure of site configuration metadata. This issue has been fixed in versions 2026.1.4, 2026.3.1, 2026.4.1 and 2026.5.0-latest.1.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Discourse form templates API exposed names and content of restricted templates to authenticated users, fixed in versions 2026.1.4, 2026.3.1, 2026.4.1, and 2026.5.0-latest.1.

Vulnerability

In Discourse versions prior to 2026.1.4, 2026.3.1, 2026.4.1, and 2026.5.0-latest.1, the form templates API endpoints (/form-templates.json) lacked authorization checks. An authenticated user on an instance with the form templates feature enabled could query the API and retrieve templates that were restricted to categories they were not authorized to access. The issue was due to the controller fetching all FormTemplate records without scoping to user-accessible categories [1][2].

Exploitation

An authenticated user needs only to be logged in on a Discourse instance with enable_form_templates set to true. By making a GET request to /form-templates.json (or accessing a specific template by ID), the API returns the name and structured content of form templates that are assigned only to private categories or categories the user cannot read. No additional privileges or user interaction are required beyond standard authentication [1].

Impact

Successful exploitation results in disclosure of form template names and their structured content (YAML/JSON definitions). This leaks site configuration metadata that was intended to be restricted to certain categories. The impact is limited to information disclosure and does not allow modification or escalation of privileges [2].

Mitigation

The vulnerability has been fixed in Discourse versions 2026.1.4, 2026.3.1, 2026.4.1, and 2026.5.0-latest.1. The fix introduces an accessible_form_templates scope that returns only templates assigned to categories the current user can read or templates not assigned to any category [1]. Users are advised to update to one of the patched versions. No workarounds other than upgrading are documented [2].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

1
ae5c9570fb91

SECURITY: Scope form template endpoints to accessible categories

https://github.com/discourse/discourseIsaac JanzenMay 18, 2026via nvd-ref
2 files changed · +58 2
  • app/controllers/form_templates_controller.rb+14 2 modified
    @@ -5,14 +5,14 @@ class FormTemplatesController < ApplicationController
       before_action :ensure_form_templates_enabled
     
       def index
    -    form_templates = FormTemplate.all.order(:id)
    +    form_templates = accessible_form_templates.order(:id)
         render_serialized(form_templates, FormTemplateSerializer, root: "form_templates")
       end
     
       def show
         params.require(:id)
     
    -    template = FormTemplate.find_by(id: params[:id])
    +    template = accessible_form_templates.find_by(id: params[:id])
     
         raise Discourse::NotFound if template.nil?
     
    @@ -23,6 +23,18 @@ def show
     
       private
     
    +  def accessible_form_templates
    +    unassigned = FormTemplate.where.not(id: CategoryFormTemplate.select(:form_template_id))
    +    accessible =
    +      FormTemplate.where(
    +        id:
    +          CategoryFormTemplate.where(category_id: Category.secured(guardian)).select(
    +            :form_template_id,
    +          ),
    +      )
    +    unassigned.or(accessible)
    +  end
    +
       def ensure_form_templates_enabled
         raise Discourse::InvalidAccess.new unless SiteSetting.enable_form_templates
       end
    
  • spec/requests/form_templates_controller_spec.rb+44 0 modified
    @@ -23,6 +23,37 @@
     
             expect(templates).to eq(form_templates)
           end
    +
    +      context "with private and public category templates" do
    +        fab!(:group)
    +        fab!(:private_category) { Fabricate(:private_category, group: group) }
    +        fab!(:private_template) do
    +          Fabricate(
    +            :form_template,
    +            name: "Private Template",
    +            template: "---\n- type: input\n  id: secret\n",
    +          )
    +        end
    +        fab!(:public_category, :category)
    +        fab!(:public_template) { Fabricate(:form_template, name: "Public Template") }
    +
    +        before do
    +          private_category.form_templates << private_template
    +          public_category.form_templates << public_template
    +        end
    +
    +        it "does not return templates only associated with private categories" do
    +          get "/form-templates.json"
    +          expect(response.status).to eq(200)
    +          returned_templates = response.parsed_body["form_templates"]
    +          returned_ids = returned_templates.map { |t| t["id"] }
    +          expect(returned_ids).to include(public_template.id)
    +          expect(returned_ids).not_to include(private_template.id)
    +          expect(returned_templates.map { |t| t["template"] }).not_to include(
    +            private_template.template,
    +          )
    +        end
    +      end
         end
     
         context "when you are not logged in" do
    @@ -61,6 +92,19 @@
             expect(current_template["template"]).to eq(form_template.template)
           end
     
    +      context "with a template only in an inaccessible private category" do
    +        fab!(:group)
    +        fab!(:private_category) { Fabricate(:private_category, group: group) }
    +        fab!(:private_template) { Fabricate(:form_template, name: "Secret Template") }
    +
    +        before { private_category.form_templates << private_template }
    +
    +        it "returns 404" do
    +          get "/form-templates/#{private_template.id}.json"
    +          expect(response.status).to eq(404)
    +        end
    +      end
    +
           context "when using tag groups in a form template" do
             fab!(:admin)
             fab!(:tag1) { Fabricate(:tag, description: "A Tag 1 custom Translation") }
    

Vulnerability mechanics

Root cause

"Missing authorization check in the form templates controller allows authenticated users to retrieve templates associated with private or restricted categories they cannot access."

Attack vector

An authenticated user on a Discourse instance with the form templates feature enabled can call the `/form-templates` and `/form-templates/:id` endpoints to retrieve the name and full YAML body of form templates. Prior to the fix, these endpoints queried `FormTemplate.all` and `FormTemplate.find_by(id:)` without scoping results through `Category.secured(guardian)`. This allowed a user to enumerate templates that were attached to private or restricted categories they were not authorized to access [CWE-862]. The impact is limited to disclosure of site configuration metadata (template names and structured content).

Affected code

The vulnerability exists in `app/controllers/form_templates_controller.rb` in the `index` and `show` actions. The `index` action called `FormTemplate.all.order(:id)` and the `show` action called `FormTemplate.find_by(id: params[:id])`, neither of which filtered by category visibility. The patch adds an `accessible_form_templates` method that scopes results through `Category.secured(guardian)`.

What the fix does

The patch introduces a new `accessible_form_templates` private method in `FormTemplatesController` that scopes results through `Category.secured(guardian)` [patch_id=443870]. It computes two sets: unassigned templates (those not linked to any category via `CategoryFormTemplate`) and templates whose category is accessible to the current user. Both the `index` and `show` actions now call `accessible_form_templates` instead of querying `FormTemplate` directly. This ensures that a template attached only to a private or restricted category returns a 404 to unauthorized users, matching the authorization pattern used elsewhere in the codebase.

Preconditions

  • authThe attacker must be an authenticated user on the Discourse instance.
  • configThe Discourse instance must have the form templates feature enabled (`SiteSetting.enable_form_templates`).

Generated on May 19, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.