Repository navigation
💥 feat(routing): what declares nothing is for administrators (3.0) - #125
Merged
Merged
Conversation
gfazioli
added a commit
that referenced
this pull request
Oct 6, 2026
- the REST folder is the one config/api.php names (api.custom.path), not always api/; a path that is not a literal is reported; - a config/routes.php or config/menus.php that does not return a literal array is a review item, never "nothing to change"; - only a key of the options argument counts as a permission_callback, not the string anywhere in the call; options that are not a literal array are reported.
gfazioli
added a commit
that referenced
this pull request
Oct 6, 2026
…tom.path The REST folder is read from the custom block's own path key, and only when its value is one whole string literal: another path key earlier in config/api.php, or a concatenation, made the report scan the wrong folder.
gfazioli
added a commit
that referenced
this pull request
Oct 6, 2026
The report said "Nothing to change" where something did: - a config entry whose key is a constant or an interpolation made the next entries' keys count for the previous one: such a file is now unreadable, as one that returns a variable is; - the first return in the file was taken, an ABSPATH guard's included: only the file's own top-level return counts, and only one; - 'capability' => null or '' counted as declared (the providers fall back to manage_options), and so did 'permission_callback' => null; - a Route imported under another name, written in lower case, or nested in another call's arguments was not read; an attribute in the arguments made a declared route look undeclared; - a pages/ capability() was found by a regex on the raw text: in a comment it counted, a protected one or one with required arguments counted, an implicitly public one did not. Read from the tokens now. Tests: the menu spy kept one capability per slug, so the first item hid the menu's own (a hard-coded 'read' in add_menu_page() passed); it keeps both, and the post-type branch has a test. The REST status stub returns 418, so a hard-coded 401 would fail.
Breaking, for 3.0 (audit S2, S3, S9): - a page of config/routes.php or of pages/ without a capability asks for manage_options; it asked for read (2.1.2), and before that for nothing; - a menu of config/menus.php without a capability asks for manage_options, and its items with it; it asked for read; - a REST route without a permission_callback refuses every request with WordPress's rest_forbidden (401 or 403), and the notice says how to make it public; up to 2.x it was public. A page, menu or route meant for every logged-in user, or for everyone, says so: 'capability' => 'read', capability() returning 'read', or 'permission_callback' => '__return_true'.
The pages of config/routes.php and pages/, the menus of config/menus.php and the REST routes of api/ that declare no capability or permission_callback, one line each with the file (and the line, for a route), read from the tokens. Nothing is rewritten: who may open a page is the author's call.
- the REST folder is the one config/api.php names (api.custom.path), not always api/; a path that is not a literal is reported; - a config/routes.php or config/menus.php that does not return a literal array is a review item, never "nothing to change"; - only a key of the options argument counts as a permission_callback, not the string anywhere in the call; options that are not a literal array are reported.
…tom.path The REST folder is read from the custom block's own path key, and only when its value is one whole string literal: another path key earlier in config/api.php, or a concatenation, made the report scan the wrong folder.
The report said "Nothing to change" where something did: - a config entry whose key is a constant or an interpolation made the next entries' keys count for the previous one: such a file is now unreadable, as one that returns a variable is; - the first return in the file was taken, an ABSPATH guard's included: only the file's own top-level return counts, and only one; - 'capability' => null or '' counted as declared (the providers fall back to manage_options), and so did 'permission_callback' => null; - a Route imported under another name, written in lower case, or nested in another call's arguments was not read; an attribute in the arguments made a declared route look undeclared; - a pages/ capability() was found by a regex on the raw text: in a comment it counted, a protected one or one with required arguments counted, an implicitly public one did not. Read from the tokens now. Tests: the menu spy kept one capability per slug, so the first item hid the menu's own (a hard-coded 'read' in add_menu_page() passed); it keeps both, and the post-type branch has a test. The REST status stub returns 418, so a hard-coded 401 would fail.
gfazioli
force-pushed
the
feat/v3-secure-defaults
branch
from
October 6, 2026 14:03
4f086eb to
a779c5a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For 3.0, on
v3: what declares nothing is for administrators. This is the breaking half of audit S2, S3 and S9. 2.1.2 (#123) shipped the half that changes nothing.💥 The defaults
config/routes.phporpages/(nocapabilitykey, nocapability()method)readsince 2.1.2)manage_optionsconfig/menus.php(nocapability); its items inherit itreadmanage_optionspermission_callbackrest_forbidden(rest_authorization_required_code(): 401 for a visitor, 403 when logged in), and the notice says how to make it publicTo keep something open, declare it:
'capability' => 'read'for a route page or a menu;capability()returning'read'for apages/class;'permission_callback' => '__return_true'for a REST route.The 14 boilerplates already declare all of these (checked by running the new report on them).
✨
php bones migrate:to-v3lists themThe command now also lists every page, menu and REST route that declares nothing, one line each, with the file and, for a route, the line. It reads them from the tokens, because including the config files would need WordPress, and it rewrites nothing: who may open a page is the author's call.
Checked on scratch copies of four boilerplates:
capabilityand__return_truestripped: every one is listed, and the route that has a callback of its own (/protected) is not.Tests
composer test: 307 tests and 824 assertions, against 287 and 765 onv3(rebased on 2.1.3).AdminMenuProviderTest(it fails onv3's provider) andMigrateToV3AccessTest.v3-access-live-smoke.sh:pages/class and a menu that declare nothing, and a REST route without a callback, each next to one that declaresreador__return_true.Review
Copilot: the account's quota is exhausted.
Codex (gpt-6-sol, high), round 1 (0cf2d5e), three P2s, all in the report:
api/instead of the folderconfig/api.phpnames;permission_callbackanywhere in a call counted as declared.Codex, round 2 (973b96b), two P2s: only
custom.pathnames the folder, and only when its value is one whole literal.An independent review (a subagent with a clean context) found more, fixed in a779c5a. The report read some files wrong:
ABSPATHguard'sreturn [];'capability' => nulland'permission_callback' => null;Route;capability()in a comment,protected, or with arguments.It also found two weak tests. The menu test's spy could not see
add_menu_page()'s capability, and the REST status stub returned the value the code would hard-code.Rebased on
v3after 2.1.3:composer testgives 307 tests and 824 assertions, andv3-access-live-smoke.sh18 ✓.