Fix 3.2.23 Content Permissions regressions: REST meta auth + protected-posts SQL (#238) - #239
Conversation
The auth_callback for `_members_access_role` bailed on core's `$allowed` flag, which is `! is_protected_meta()` and therefore always false for the underscore-prefixed key. This denied every user (including admins), so the field could never be saved via REST — block editor, Elementor, and other page builders failed with "you are not allowed to edit the _members_access_role custom field." Remove the bail so access is granted from the actual capability checks (restrict_content + edit_post). No change to the CVE-2026-12426 read-side SQL fix or the #220 block-editor rework. Fixes #238 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR SummaryMedium Risk Overview The REST collection hiding of protected posts drops the deep correlated SQL ( Version and changelog updated to 3.2.24. Reviewed by Cursor Bugbot for commit de557e6. Bugbot is set up for automated code reviews on this repo. Configure here. |
ThemeGravity
left a comment
There was a problem hiding this comment.
@cartpauj Code looks good to me.
…#238) The CVE-2026-12426 fix (#232) excluded protected posts from REST collections by mirroring members_can_user_view_post() in SQL, walking the post hierarchy with up to 14-deep correlated subqueries per row. On large sites this caused severe DB load and timeouts, and the query could exceed MySQL's join/subquery limits and fail — returning zero posts even when nothing was restricted. Replace the per-row subquery walk with members_get_rest_hidden_post_ids(): resolve the (usually small) set of posts carrying a role restriction, reuse the vetted PHP permission logic, expand to inheriting descendants via a bounded BFS, then exclude a flat `ID NOT IN (...)` list. Pagination counts stay accurate, so the side channel remains closed. Adds a members_rest_hidden_post_ids filter. Removes the members_sql_* helpers, the ancestor-depth global, and members_build_rest_protected_posts_exclusion_sql. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
members_get_rest_hidden_post_ids() scoped the restriction-root query to the
queried post type. A query with post_type => 'any' would have produced
`post_type IN ('any')`, matched nothing, and returned zero hidden IDs — leaking
protected posts. Treat 'any' (and an unset type) as no scoping so all restricted
posts are considered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.
Reviewed by Cursor Bugbot for commit de557e6. Configure here.
| * @return string | ||
| */ | ||
| function members_sql_post_has_access_role_meta( $post_id_sql ) { | ||
|
|
There was a problem hiding this comment.
REST hidden IDs skip cross-type inheritance
High Severity
The members_get_rest_hidden_post_ids function limits its initial search for restricted posts by the queried post_type. This misses posts that inherit restrictions from parents of other types, leading to inaccurate X-WP-Total and found_posts counts in REST API responses.
Reviewed by Cursor Bugbot for commit de557e6. Configure here.


Fixes #238
Addresses three 3.2.23 regressions that are driving the current wave of WP.org reports. Two failure signatures were showing up: "can't save / not allowed to edit the custom field" (the auth bug) and "editor/DB timeouts + zero posts" (the CVE SQL rework). This PR covers both.
WordPress.org reports:
1. Content Permissions cannot be saved via REST (#238)
The
auth_callbackfor_members_access_role(added in #220) began withif ( ! $allowed ) return false;. Core passes$allowed = ! is_protected_meta( $meta_key ), which is always false for the underscore-prefixed key — so it denied every user, including admins. The field could never be written via REST (block editor, Elementor, page builders).Fix: drop the
! $allowedbail; grant from the real capability checks (restrict_content+edit_post, or create rights for new posts).2. REST exclusion caused severe DB load / timeouts on large sites
The CVE-2026-12426 fix (#232) excluded protected posts by mirroring
members_can_user_view_post()in SQL, walking the hierarchy with up to 14-deep correlated subqueries per row (ancestor_depth = 15). Pathological on large sites.3. Same query returned zero posts even when nothing was restricted
That SQL could exceed MySQL's 61-table join limit, error out, and return an empty result set — hiding everything.
Fix for 2 & 3: replace the per-row subquery walk with
members_get_rest_hidden_post_ids()— resolve the (usually small) set of posts that actually carry a role restriction, reuse the vetted PHP permission logic (members_can_user_view_post(), so custom filters are honored), expand to inheriting descendants via a bounded BFS, and exclude a flatID NOT IN (...)list. Pagination counts stay accurate, so the CVE side channel remains closed. Removed themembers_sql_*helpers, the ancestor-depth global, andmembers_build_rest_protected_posts_exclusion_sql. Added amembers_rest_hidden_post_idsfilter.Scope / safety
Testing notes
wp-includes/capabilities.php), where$allowed = ! is_protected_meta().members_can_user_view_post()across nested satisfiable/unsatisfiable restriction roots (descent stops at governing roots, resumes at nested restricted roots) — matches exactly.🤖 Generated with Claude Code