GHSA-mqq5-j7w8-2hgh
HIGHGHSA-mqq5-j7w8-2hgh is a high-severity (CVSS 7.5) vulnerability in alchemy_cms. O3 Security confirms whether GHSA-mqq5-j7w8-2hgh is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
AlchemyCMS: Unauthenticated nested page API leaks restricted & unpublished content
Blast Radius
alchemy_cms💎alchemy_cms💎alchemy_cms💎alchemy_cmsReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects RubyGems packages — download data is not available via public APIs for these ecosystems.
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).
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 💎RubyGems | alchemy_cms | ≥ 8.2.0&&< 8.2.6 | 8.2.6 |
| 💎RubyGems | alchemy_cms | ≥ 8.1.0&&< 8.1.14 | 8.1.14 |
| 💎RubyGems | alchemy_cms | ≥ 8.0.0.a&&< 8.0.15 | 8.0.15 |
| 💎RubyGems | alchemy_cms | all versions | 7.4.15 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for alchemy_cms. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update alchemy_cms to 8.2.6 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-mqq5-j7w8-2hgh is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-mqq5-j7w8-2hgh is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-mqq5-j7w8-2hgh. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-mqq5-j7w8-2hgh in your dependencies?
O3 detects GHSA-mqq5-j7w8-2hgh across RubyGems dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.