XML-RPC: wp.getUsersBlogs, enforce arg string requirements - #12462
XML-RPC: wp.getUsersBlogs, enforce arg string requirements#12462josephscott wants to merge 5 commits into
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
There was a problem hiding this comment.
🤖 AI-generated code review. Produced by Claude Code using the Claude Opus 5 (1M context) model (claude-opus-5[1m]), run by @westonruter. Findings were verified empirically against a local checkout (guard temporarily disabled, tests re-run) rather than by inspection alone; the verification notes are inline below. Please treat it as input to human review, not a substitute for it.
Overview
Adds a type guard to wp_xmlrpc_server::wp_getUsersBlogs() rejecting non-string username/password with IXR_Error( 400, … ), plus a new test file with a 4-case data provider.
Verification performed:
- The bug is real: with the guard removed,
wp_getUsersBlogs( array( 'user', array() ) )throwsTypeError: trim(): Argument #1 ($string) must be of type string, array givenfromwp_authenticate()(src/wp-includes/pluggable.php:690). The guard fixes it. composer lintclean; PHPStan reports no errors on the changed lines; the new tests pass 4/4.
Issues
1. The fix is one layer too low — every other authenticated method still fatals
Confirmed empirically: wp_getPosts( array( 1, 'user', array() ) ) throws the identical TypeError. So do the other authenticated methods, because they all funnel through wp_xmlrpc_server::login(), which passes straight to wp_authenticate().
Guarding inside login() fixes all of them in one place, and needs no new translatable string:
if ( ! is_string( $username ) || ! is_string( $password ) ) {
$this->error = new IXR_Error( 403, __( 'Incorrect username or password.' ) );
$this->auth_failed = true;
return false;
}If the narrow scope is deliberate for this ticket, it's worth stating that on Trac so the broader case isn't lost.
2. BC break: integer credentials that authenticate today start returning 400
Confirmed: with the guard removed, a user whose password is 12345 authenticates successfully via wp_getUsersBlogs( array( 'numuser', 12345 ) ). With the guard it returns a 400. IXR_Message parses <int> / <i4> / <double> / <boolean> into PHP scalars, so any client that types a numeric password as <int> regresses.
Only arrays and objects cause the fatal — scalars coerce fine (trim( 12345 ) → "12345"). is_scalar() would fix the crash with no behavior change for existing working clients:
if ( ! is_scalar( $args[0] ) || ! is_scalar( $args[1] ) ) {The test explicitly pins the int rejection ('an integer as password'), so this reads as a deliberate choice — it should be called out on the ticket rather than left implicit.
3. Objects are the untested fatal trigger, and they're reachable from the wire
class-IXR-message.php:194 turns <dateTime.iso8601> into an IXR_Date object, so an object is not a synthetic case — a client can put one in the password slot. trim( $object ) is the same TypeError. The data provider covers arrays and an int but no object; adding a new stdClass (or IXR_Date) case would cover the other half of the actual crash surface.
4. Test docblock overstates the array-username case
Verified: array( array(), '12345' ) does not fatal. sanitize_user() → wp_strip_all_tags() already guards non-scalars, so it emits an E_USER_WARNING and falls through to a normal 403. Only the password fatals. The docblock — "instead of triggering a fatal error" — is accurate for the password only. Hardening the username is still correct; just worth rewording.
Style / conventions
Measured against the sibling tests/phpunit/tests/xmlrpc/demo/addTwoNumbers.php, which uses the same error-code and message pattern:
- Assert the message, not just the code.
minimum_args()also returns400('Insufficient arguments…'), soassertSame( 400, $result->code )alone doesn't prove which branch fired.addTwoNumbersasserts$result->messagetoo — same here. - Missing
@covers wp_xmlrpc_server::wp_getUsersBlogsclass-level annotation (the sibling has it). - Provider return type: the sibling uses
public function data_valid_integers(): array. Considerpublic static function data_non_string_credentials(): array— static is PHPUnit 10-ready (core currently has ~196 static vs ~959 non-static providers, so either passes review today). - Double space in
'…method. Requires two strings.'— inconsistent with the existing'…method. Requires two integers.'inaddTwoNumbers(). make_user_by_role( 'subscriber' )in the test is dead setup — the guard returns before authentication. Harmless, but it implies a dependency that isn't there.
Missing coverage
- No positive case confirming valid string credentials still delegate correctly to
blogger_getUsersBlogs(). Worth one test so a future tightening of the guard can't silently break the happy path. - No
null,float,bool, or object cases. - No multisite (
@group ms-required) run. The guard precedes theis_multisite()branch so this is low risk, but the paths diverge right after it.
Security & performance
Net positive: converts a PHP fatal — trivially triggerable by an unauthenticated request, and capable of leaking a stack trace with display_errors on — into a controlled 400. Two is_string() calls, no measurable cost. The error message is a static translated string with no user input reflected.
Recommendation
Sound fix for the reported symptom. Before landing, I'd want the is_scalar vs is_string BC question settled on the ticket (issue 2), an object case in the provider (issue 3), and message assertions in the tests. Moving the guard into login() (issue 1) is the higher-value change if the ticket scope allows it.
https://core.trac.wordpress.org/ticket/65600
AI assistance: Yes
Tool(s): Claude
Model(s): Opus 4.8
Used for: Writing the unit tests
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.