Skip to content

fix(permissions): hide unreadable pages from menu and breadcrumb - #4138

Open
tvdeyen wants to merge 1 commit into
mainfrom
frontend-menu-breadcrumb-permitted-roles
Open

fix(permissions): hide unreadable pages from menu and breadcrumb#4138
tvdeyen wants to merge 1 commit into
mainfrom
frontend-menu-breadcrumb-permitted-roles

Conversation

@tvdeyen

@tvdeyen tvdeyen commented Aug 7, 2026

Copy link
Copy Markdown
Member

What is this pull request for?

Follow-up to the security review of #4112. Page read access is enforced in the authorization layer, but two frontend helpers rendered navigation without consulting it, so a restricted page (now including one limited to certain roles) still leaked its name and url:

  • render_breadcrumb listed every published ancestor of the current page.
  • render_menu rendered every child node, and the menu fragment cache key did not include the user.

Both now filter by the current user's read permission via Page#readable_by?, which keeps the check a column read (no extra query) and automatically honors permitted_roles.

Notable changes

  • render_breadcrumb drops ancestors the current user cannot read.
  • Alchemy::Node#readable_children(user) returns only children whose page is readable (external nodes without a page are always kept). The generated menu partials (wrapper/node, erb/haml/slim) render through it and add the current user to their cache key, so a filtered menu is never served to a different user.
  • Because menu partials are generated into the host app, the engine cannot rewrite existing apps' copies. An 8.4 upgrade todo (bin/rails alchemy:upgrade) tells maintainers to apply the same change (or regenerate with bin/rails g alchemy:menus). The dummy app's partials are updated so the behavior is covered by specs.

Specs cover: breadcrumb hides a restricted ancestor from a guest but shows it to a permitted member; a menu node to a restricted page is hidden from a guest and shown to a permitted member; Node#readable_children filtering; and the upgrade todo.

Note

Stacked on #4112 — this depends on Page#readable_by? from the permitted-roles branch, so its base is permitted-roles-for-restricted-pages. Merge after #4112; GitHub will retarget the base to main once #4112 lands.

Checklist

  • I have followed Pull Request guidelines
  • I have added a detailed description into each commit message
  • I have added tests to cover this change

render_breadcrumb listed every published ancestor and the menu partials
rendered every child node, so a restricted page a user may not read still
leaked its name and url through navigation. Both now filter by the current
user's read permission, using readable_by? so the check stays a column read
with no extra query and honors permitted_roles.

render_breadcrumb drops ancestors the current user cannot read. A new
Alchemy::Node#readable_children returns only children whose page is readable
(external nodes without a page are always kept), and the generated menu
partials render through it and add the current user to their cache key, so a
filtered menu is never served to a different user. Because those partials are
generated into the host app, an 8.4 upgrade todo tells existing apps to apply
the same change to their own menu partials.
@tvdeyen
tvdeyen requested a review from a team as a code owner August 7, 2026 12:47
Base automatically changed from permitted-roles-for-restricted-pages to main August 7, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant