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(expand)+ 1 more
- (no CPE)
- (no CPE)range: <2026.1.4, 2026.3.1, 2026.4.1, 2026.5.0-latest.1
Patches
1ae5c9570fb91SECURITY: Scope form template endpoints to accessible categories
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
2News mentions
0No linked articles in our index yet.