Skip to content

AlchemyCMS: Unauthenticated nested page API leaks restricted & unpublished content

High severity GitHub Reviewed Published Jun 18, 2026 in AlchemyCMS/alchemy_cms

Package

alchemy_cms (RubyGems)

Affected versions

>= 8.2.0, <= 8.2.5
>= 8.1.0, <= 8.1.13
>= 8.0.0.a, <= 8.0.14
<= 7.4.14

Patched versions

8.2.6
8.1.14
8.0.15
7.4.15

Description

Unauthenticated nested page API leaks restricted & unpublished content

  • Location: app/controllers/alchemy/api/pages_controller.rb:28 (Api::PagesController#nested)
  • Affected version: Alchemy CMS 8.3.0.dev (Rails 8.1.3)

Description

The unauthenticated GET /api/pages/nested endpoint returns the full page tree to any anonymous caller, including restricted (member-only) pages and unpublished/draft pages that should be hidden.
Appending ?elements=true additionally dumps the element/ingredient content of restricted pages, fully bypassing the access control the sibling show and index actions enforce.

Root cause

Api::PagesController#nested calls no authorize! and applies no published/restricted scoping, unlike show (authorize! :show) and index (accessible_by(current_ability, :index)).
PageTreePreloader loads page.self_and_descendants unfiltered, and PageTreeSerializer emits every page's metadata (and, with elements, public_version.elements) with no ability check.

Evidence

An unauthenticated GET /api/pages/nested returns HTTP 200 with the restricted page ("restricted":true) and an unpublished draft ("public":false); ?elements=true leaks its content (e.g. TOPSECRET_RESTRICTED_BODY_proof123).
The same guest hitting GET /api/pages/3 (show) gets HTTP 403 {"error":"Not authorized"}, proving nested returns what show correctly denies.

Reproduction

# 1) Metadata leak (guest, no auth)
curl -s http://localhost:3000/api/pages/nested | python3 -m json.tool | grep -E '"name"|"restricted"|"public"'

# 2) Content leak of restricted page
curl -s "http://localhost:3000/api/pages/nested?elements=true" | grep -oE 'TOPSECRET_RESTRICTED_BODY_[A-Za-z0-9]+|RESTRICTED_RICHTEXT_[A-Za-z0-9]+'

# 3) Contrast — show denies the same guest
curl -s -o /dev/null -w "show /api/pages/3 -> HTTP %{http_code}\n" http://localhost:3000/api/pages/3

Suggested fix

def nested
  @page = Page.find_by(id: params[:page_id]) || Language.current_root_page
  authorize! :show, @page
  preloaded_page = PageTreePreloader.new(page: @page, user: current_alchemy_user, ability: current_ability).call
  render json: PageTreeSerializer.new(preloaded_page, ability: current_ability,
                                      user: current_alchemy_user, elements: params[:elements])
end

Additionally scope PageTreePreloader's self_and_descendants via accessible_by(current_ability) and gate element emission in PageTreeSerializer#page_elements behind opts[:ability].can?(:show, page).

References

@tvdeyen tvdeyen published to AlchemyCMS/alchemy_cms Jun 18, 2026
Published to the GitHub Advisory Database Jun 19, 2026
Reviewed Jun 19, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

EPSS score

Weaknesses

Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action. Learn more on MITRE.

CVE ID

No known CVE

GHSA ID

GHSA-mqq5-j7w8-2hgh

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.