fix(admin): make governance capabilities readable on the user profile - #205
Conversation
WP Sudo grants its four governance capabilities directly to users via
WP_User::add_cap() (activation, upgrade, the Access-tab grant handler).
WordPress core's profile screen (wp-admin/user-edit.php) rendered those
per-user caps as a run-on list of raw slugs under a generic "Additional
Capabilities" heading — unattractive and unexplained.
Add two profile-screen callbacks in Admin::register():
- suppress_governance_capabilities_display() on the core
`additional_capabilities_display` filter. Returns false to hide the raw
section ONLY when every non-role capability the profile user holds is a
truthy governance cap. If any foreign additional cap is present it returns
the incoming value unchanged (never hides other plugins' caps, never
overrides an already-false), and an explicitly denied governance cap is
left visible as core's "Denied: ...". Non-role classification uses
wp_roles()->is_role() to mirror core exactly, so additional custom roles
are not mistaken for stray capabilities.
- render_governance_capabilities_profile_note() on show_user_profile and
edit_user_profile. Renders a "Sudo capabilities" block listing each
directly-granted capability by its get_cap_label() label. Reads the raw
stored grants rather than an effective check, so a multisite super admin
(effective access via the wp_sudo_can() short-circuit, no stored grant) is
not misreported. The Access-tab link is shown only to a viewer who can
reach it (wp_sudo_can('manage_wp_sudo')); others see plain text.
Display-only: enforcement is untouched. Adds 12 unit tests covering the
hide/pass-through/denied/custom-role/no-cap and render branches, one
psalm-baseline entry for the added unqualified wp_sudo_can() call, a 4.8.0
CHANGELOG entry, and refreshed counts in docs/current-metrics.md.
Verified the core filter and its `count(caps) > count(roles) && true ===
$display` render condition against WordPress core wp-admin/user-edit.php
(raw.githubusercontent.com/WordPress/WordPress/master/wp-admin/user-edit.php).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TCJoA72EDAmLviMKDB5zx
🎮 Try in WordPress PlaygroundPreview is pinned to WordPress Uses the checked-in Logs in as ✅ What you can test
❌ What won't work
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #205 +/- ##
============================================
+ Coverage 90.07% 90.14% +0.07%
- Complexity 1540 1554 +14
============================================
Files 20 20
Lines 5856 5898 +42
============================================
+ Hits 5275 5317 +42
Misses 581 581
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
… note render_governance_capabilities_profile_note() builds the Sudo Access link through network_admin_url() on multisite and admin_url() on single-site. The existing render tests all run single-site (TestCase defaults is_multisite() to false), leaving the multisite branch uncovered (flagged by Codecov on #205). Add a test that flips is_multisite() true and asserts the link resolves through /wp-admin/network/ with tab=access. Refresh the unit-test/line counts in docs/current-metrics.md accordingly (1,041 tests / 3,131 assertions). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TCJoA72EDAmLviMKDB5zx
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c22acb33aa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
) * docs: reconcile release-status + release-environment-log to v4.8.0 Both canonical release docs were stale. release-status.md still named 4.7.0 as latest with "nothing unreleased past it"; release-environment-log.md stopped at 4.6.0 and its status table contradicted its own 4.6.0 section ("gate pending" vs "cleared"). release-status.md: - Latest tagged release -> v4.8.0 (cut 2026-07-23, on 10587a4); previous -> v4.7.0 - Add 4.8.0 payload note (REST-gate security hardening #213/#214/#215 + role/capability lockdown audit MVP #206 + readable governance caps #205) - Add v4.8.0 tag checklist; flag the env-matrix decision as a retroactive gap - Current main state: record unreleased-past-tag work (alert-bridge role-drift push #226 + 4.8.0 security-test docs); correct "nothing unreleased" claim - Update post-tag drift source to v4.8.0..main (no longer empty) release-environment-log.md: - Fix stale 4.6.0 status-table row (tagged + cleared, not "gate pending") - Add v4.7.0 row/section (matrix reused from 4.6.0 by conscious decision, per release-status.md v4.7.0 checklist item 2) - Add v4.8.0 row/section: manual Apache/managed-host/min-WP matrix decision not recorded at tag time; 4.8.0 changed server-facing REST routing/method matching, so reuse is not obviously justified. Live functional security pass exists (security-test-results-4.8.0.md, Studio/SQLite+Redis, headless) but is not the environment matrix. Flagged for maintainer reuse-or-rerun decision. Verified against: git tag v4.8.0 (annotated, 2026-07-23), wp-sudo.php / readme.txt version constants on main, CHANGELOG 4.8.0 section, git log v4.7.0..v4.8.0 and v4.8.0..main, docs/security-test-results-4.8.0.md. Docs-only; both files are in the docs-lint date/count allowlist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: address Codex review — fix email-rule description, WP 7.0.2, and 4 contradictions Incorporates the automated Codex review findings on #231: 1. (correctness) 4.8.0 payload note conflated two REST fixes. Corrected: user.change_password/user.promote were PRE-EXISTING rules matching only PUT/PATCH (POST ungated) → now gate POST (#213); user.change_email is a NEW rule gating POST/PUT/PATCH (#214). Verified against the CHANGELOG 4.8.0 section. 2. (accuracy) Latest stable WordPress patch was stale ("7.0 GA, June 14"). Updated to 7.0.2, verified via api.wordpress.org/core/version-check on 2026-07-24 (matches the WP 7.0.2 env in security-test-results-4.8.0.md). Clarified that readme.txt "Tested up to 7.0" tracks the branch, not the patch. 3. (contradiction) v4.8.0 checklist intro claimed "all items done" while item 2 is the open env-matrix gap — reworded to except item 2. 4. (contradiction) historical 4.5.0 section still said main was "at v4.7.0 with nothing unreleased" — updated to 4.8.0 + unreleased post-tag work. 5. (contradiction) env-log 4.6.0 section opened "no v4.6.0 tag cut yet" after its row was marked tagged — reframed as the pre-tag gate-decision record. 6. (consistency) nginx-multisite smoke lane: release-status said "de-scoped" but ground truth (release-confidence.yml + env-log) shows it was re-hardened and restored to the gate post-4.6.0 (2026-07-06). release-status now matches. Docs-only; both files remain in the docs-lint date allowlist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Summary
manage_wp_sudo,view_wp_sudo_activity,export_wp_sudo_activity,revoke_wp_sudo_sessions) directly to users viaWP_User::add_cap(). WordPress core's profile screen (wp-admin/user-edit.php) rendered those per-user caps as a run-on list of raw slugs under the generic "Additional Capabilities" heading. This adds two profile-screen callbacks inAdmin::register():suppress_governance_capabilities_display()on the coreadditional_capabilities_displayfilter — hides the raw section only when every non-role capability the profile user holds is a truthy governance cap. Any foreign additional cap → returns the incoming value unchanged (never hides other plugins' caps, never overrides an already-hidden section); a denied governance cap stays visible as core's "Denied: …". Non-role classification useswp_roles()->is_role()to mirror core, so additional custom roles aren't mistaken for stray caps.render_governance_capabilities_profile_note()onshow_user_profile+edit_user_profile— renders a readable "Sudo capabilities" block listing each directly granted capability by itsget_cap_label()label, reading raw stored grants (not effective checks) so a multisite super admin isn't misreported. The Access-tab link is shown only to a viewer who can reach it (wp_sudo_can('manage_wp_sudo')); others see plain text.Validation
composer test— 1,040 unit tests / 3,129 assertions pass (12 new tests covering the hide / pass-through / denied / custom-role / no-cap suppression branches and the render branches with/without the manager link and partial grants)composer lint(PHPCS) clean on production;composer analyse(PHPStan + Psalm) cleanprofile.phprender was not exercised — an integration/browser stack (MySQL) isn't provisioned in this environment, and the change is display-only with full unit coverage of both callbacks. The WordPress core filter and itscount(caps) > count(roles) && true === $displayrender condition were verified againstwp-admin/user-edit.phpon WordPress trunk.Checklist
4.8.0CHANGELOG entry and refreshed unit-test/size counts indocs/current-metrics.mdGenerated by Claude Code