Skip to content

Fix 3.2.23 Content Permissions regressions: REST meta auth + protected-posts SQL (#238) - #239

Merged
cartpauj merged 3 commits into
developfrom
fix/content-permissions-rest-auth-238
Jul 7, 2026
Merged

Fix 3.2.23 Content Permissions regressions: REST meta auth + protected-posts SQL (#238)#239
cartpauj merged 3 commits into
developfrom
fix/content-permissions-rest-auth-238

Conversation

@cartpauj

@cartpauj cartpauj commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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:

  • https://wordpress.org/support/topic/error-with-3-2-23/ ("not allowed to edit the _members_access_role custom field")
  • "v3.2.23 REST protection query causes severe DB load / timeouts on large sites"
  • "3.2.23 regression – SQL-level REST exclusion returns zero posts when no posts have roles"

1. Content Permissions cannot be saved via REST (#238)

The auth_callback for _members_access_role (added in #220) began with if ( ! $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 ! $allowed bail; 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 flat ID NOT IN (...) list. Pagination counts stay accurate, so the CVE side channel remains closed. Removed the members_sql_* helpers, the ancestor-depth global, and members_build_rest_protected_posts_exclusion_sql. Added a members_rest_hidden_post_ids filter.

Scope / safety

  • The CVE-2026-12426 protection is preserved — same exclusion semantics, computed correctly instead of via broken SQL.
  • The Fix for #217 rest api and editor hangs #220 block-editor rework (document panel + classic meta-box suppression) is untouched; it just saves now.
  • Bumped to 3.2.24 + changelog entries.

Testing notes

  • Auth bug root cause confirmed against WP core (wp-includes/capabilities.php), where $allowed = ! is_protected_meta().
  • The hidden-ID hierarchy algorithm was unit-tested against an oracle reimplementation of members_can_user_view_post() across nested satisfiable/unsatisfiable restriction roots (descent stops at governing roots, resumes at nested restricted roots) — matches exactly.
  • Not exercised on a live multisite / large dataset locally; suggest a reviewer spot-checks REST pagination counts and a hierarchical restricted section as an anonymous user.

🤖 Generated with Claude Code

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>
@cartpauj cartpauj self-assigned this Jul 6, 2026
@cartpauj
cartpauj requested a review from ThemeGravity July 6, 2026 01:50
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches REST content-permission writes and how protected posts are excluded from API collections (security-sensitive), but behavior is intended to match prior semantics with a safer implementation.

Overview
3.2.24 fixes three regressions from 3.2.23 around Content Permissions and REST.

The _members_access_role REST auth_callback no longer returns early when WordPress passes $allowed === false for protected meta. Saving access roles via REST (block editor, Elementor, etc.) is allowed again based on restrict_content and edit_post / create rights.

REST collection hiding of protected posts drops the deep correlated SQL (members_sql_*, ancestor-depth global) in favor of members_get_rest_hidden_post_ids(): find posts with role meta, evaluate visibility with members_can_current_user_view_post(), BFS-expand inheriting descendants, then apply a flat ID NOT IN (...) clause. A new members_rest_hidden_post_ids filter can adjust the excluded set; custom members_can_user_view_post filters are honored again. Pagination totals should still match the CVE-2026-12426 side-channel fix.

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.

@cartpauj
cartpauj requested a review from cspf-services July 6, 2026 02:03

@ThemeGravity ThemeGravity left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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>
@cartpauj cartpauj changed the title Fix Content Permissions REST meta auth denying all users (#238) Jul 7, 2026
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>
@cartpauj
cartpauj merged commit ca03a60 into develop Jul 7, 2026
1 check passed
@cartpauj
cartpauj deleted the fix/content-permissions-rest-auth-238 branch July 7, 2026 16:29

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

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 ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit de557e6. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants