CVE Patch Review

GHSA-MQQ5-J7W8-2HGH: Authorization Added to Alchemy CMS Nested Pages API

GHSA-MQQ5-J7W8-2HGH · Updated 2026-06-19 Root-cause

Summary

The patch addresses a missing authorization check in Alchemy CMS's API nested pages endpoint by enforcing page-level authorization at the controller entry point and propagating ability-based filtering into tree preloading and element serialization. The change materially fixes the data exposure path described in the advisory: unauthenticated or low-privilege callers could previously enumerate nested pages and, with elements enabled, extract content from restricted descendants. The remediation is defense-in-depth rather than a single guard, and the added tests demonstrate both denial for unauthorized roots and pruning of unreadable descendants and elements.

Analysis

Vulnerability

GHSA-MQQ5-J7W8-2HGH describes a missing authorization flaw in the Alchemy CMS API pages controller. The vulnerable path was the nested pages endpoint, where the controller preloaded a full page subtree from @page without first authorizing access to that root page and without constraining descendant traversal to the caller's readable scope. As shown in the patch summary, the old implementation invoked PageTreePreloader.new(page: @page, user: current_alchemy_user).call, and the preloader itself used page.self_and_descendants directly. That combination allowed unauthenticated or underprivileged callers to dump page structure beyond their authorization boundary.

The impact was not limited to metadata enumeration. The patch also hardens the serializer to suppress elements when the provided ability cannot read the underlying page, which indicates that element content from restricted pages could otherwise be exposed when elements=true was requested. The new controller and serializer tests explicitly assert that guest users must not see restricted or unpublished pages, must not receive restricted page content, and must receive 403 when directly requesting a restricted page as the root.

# Vulnerable flow from patch summary
# controller
preloaded_page = PageTreePreloader.new(page: @page, user: current_alchemy_user).call

# service
pages = page.self_and_descendants

Sources: official patch commit, GitHub Security Advisory, third-party report.

Patch

The patch introduces authorization enforcement at three layers.

  1. Controller gate: the API controller now performs authorize! :show, @page before building the nested response. This closes the direct access case where a caller could specify a restricted page_id and receive a tree rooted at an unreadable page.

  2. Ability-scoped tree loading: PageTreePreloader now requires an ability: argument and replaces unrestricted descendant loading with page.self_and_descendants.accessible_by(ability, :read). This is the core fix for subtree overexposure because it prunes unreadable descendants before serialization.

  3. Serializer hardening: PageTreeSerializer now unwraps delegator-backed page objects and checks opts[:ability].can?(:read, authorized_page) before returning elements. If unreadable, it returns Alchemy::Element.none. This prevents content leakage even if a restricted page object reaches the serializer.

authorize! :show, @page

preloaded_page = PageTreePreloader.new(
  page: @page,
  user: current_alchemy_user,
  ability: current_ability
).call

# service
pages = page.self_and_descendants.accessible_by(ability, :read)

# serializer
authorized_page = page.try(:__getobj__) || page
return Alchemy::Element.none unless opts[:ability].can?(:read, authorized_page)

The tests added in the commit are aligned with the vulnerability mechanics. Controller specs verify that guest users do not see restricted or unpublished descendants, do not receive restricted element content, and are denied direct access to a restricted root page. Service specs verify that the preloader includes only readable children for a guest ability. Serializer specs verify that element emission is suppressed for unreadable pages even when a plain Alchemy::Page is passed instead of the usual delegator wrapper. Source: official patch commit.

Review

Pros

  • The fix addresses the actual authorization boundary failure, not just the symptom. The root page is now explicitly authorized, and descendants are filtered through accessible_by(ability, :read).
  • The remediation is defense-in-depth. Even if an unreadable page object reaches serialization, element content is still suppressed by an ability check.
  • The preloader API was tightened to require ability:, reducing the chance of future call sites accidentally loading unrestricted trees.
  • Test coverage is strong and directly tied to exploitability: unauthorized root access, descendant pruning, unpublished/restricted visibility, and element leakage are all exercised.
  • The serializer's delegator unwrapping is a practical hardening detail. It avoids authorization checks being evaluated against the wrapper class instead of Alchemy::Page.

Cons

  • The controller authorizes with :show while the preloader and serializer scope with :read. If the application's ability rules distinguish these actions, the endpoint could still exhibit inconsistent behavior between root authorization and descendant filtering. The patch likely relies on Alchemy's action aliases, but that assumption is not demonstrated in the provided sources.
  • The fix is localized to this endpoint and related helpers. It does not by itself prove that other API endpoints consuming page trees or elements consistently pass ability: and enforce equivalent checks.
  • The serializer safeguard only protects element emission. If future fields beyond elements are added and are sensitive, they will need equivalent authorization-aware handling.

Verdict

Root-cause.

This patch fixes the underlying authorization flaw in the nested pages API by enforcing access control at the root object, constraining descendant expansion to readable pages, and preventing restricted element serialization. The changes are source-consistent with the advisory and the tests demonstrate that the previously exposed data paths are now blocked for guest users while remaining available to authorized users. The only notable review point is the mixed use of :show and :read; assuming Alchemy's ability model aliases them appropriately, the patch is technically sound and substantially complete.

Sources