Skip to content

Networks and Sites: Improve search term handling in wp_ajax_autocomplete_user() - #11530

Closed
rajeshcpr wants to merge 13 commits into
WordPress:trunkfrom
rajeshcpr:rajeshcpr-patch-2
Closed

Networks and Sites: Improve search term handling in wp_ajax_autocomplete_user()#11530
rajeshcpr wants to merge 13 commits into
WordPress:trunkfrom
rajeshcpr:rajeshcpr-patch-2

Conversation

@rajeshcpr

@rajeshcpr rajeshcpr commented Apr 9, 2026

Copy link
Copy Markdown

This PR improves the handling of the term request parameter in wp_ajax_autocomplete_user() and adds full test coverage for the Ajax action. Per the analysis on the Trac ticket, the previous code was not exploitable — the search term is passed through wpdb::prepare()/wpdb::esc_like() by WP_User_Query, and the term is never reflected in the response — so this is a correctness and coding-standards improvement rather than a security fix.

Changes to wp_ajax_autocomplete_user()

  • Unslash and sanitize the search term with sanitize_text_field( wp_unslash( $_REQUEST['term'] ) ) (the originally submitted change). Unslashing fixes searches for terms containing quotes or backslashes — notably email addresses containing an apostrophe (e.g. o'brien@example.com), which are valid per is_email() but could previously never match because wp_magic_quotes() added a slash which wpdb::esc_like() then escaped as a literal.
  • Short-circuit invalid terms: a missing, non-string, or empty term now results in a 0 response instead of an empty [] array. This avoids a PHP warning for an undefined array key (or an array-to-string conversion warning when term is submitted as an array) and skips the two get_users() include/exclude queries that previously ran for nothing. This is not a change in behavior for core's own JS, since jQuery UI autocomplete enforces minLength: 2 and treats a 0 response as "no results".
  • Trim asterisks from the term before the empty check. Since the term is wrapped in * wildcards, WP_User_Query trims all leading/trailing asterisks; a term consisting only of asterisks would previously reduce to an empty search that matched all users on the network.
  • Document the behavior changes with a @since changelog entry on the function.

New tests

The new Tests_Ajax_wpAjaxAutocompleteUser class (15 tests, ms-required) covers:

  • Matching users are returned for the search autocomplete type, and the email address is returned as the value when autocomplete_field=user_email.
  • Users of the current site are excluded for the default add type.
  • HTML tags are stripped from the search term (verified via pre_get_users) while the sanitized term still matches.
  • Searching for an apostrophe-containing email address round-trips correctly (the tests populate $_GET via wp_slash() to simulate wp_magic_quotes(), proving the unslash fix).
  • A missing, empty, whitespace-only, non-string, or asterisk-only term results in a 0 response, while an asterisk-wrapped term still matches.
  • Capability gates: -1 without promote_users, -1 for site administrators by default, success when the autocomplete_users_for_site_admins filter is enabled, and -1 on large networks.

Trac ticket: https://core.trac.wordpress.org/ticket/65051

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Fable 5
Used for: Reviewing the original patch, authoring the follow-up commits (input validation, asterisk trimming, @since documentation) and the expanded test suite, and drafting this description. All changes were directed, reviewed, and edited by @westonruter.

Appendix: Original PR title and description

Title: $_REQUEST['term'] used unsanitized in user search query

User-supplied search term is concatenated directly into the get_users() search argument without
sanitize_text_field() or wp_unslash().

Trac ticket: https://core.trac.wordpress.org/ticket/65051

Fixes #65051


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.

User-supplied search term is concatenated directly into the get_users() search argument without
  sanitize_text_field() or wp_unslash().
@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown

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 props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props rajeshcp, wildworks, westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Comment thread src/wp-admin/includes/ajax-actions.php Outdated
Comment thread src/wp-admin/includes/ajax-actions.php Outdated
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The 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

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

t-hamano and others added 2 commits August 1, 2026 19:06
The user autocomplete Ajax endpoint had no test coverage, so the search
term sanitization added on this branch was unverified. These tests lock
in that behavior: HTML tags are stripped before the term reaches
get_users(), and a missing `term` request variable no longer raises a
PHP warning. Both fail without the sanitization change.

The remaining tests document the surrounding contract that the
sanitization must not break: the response shape, the `add` vs `search`
autocomplete types, the `user_email` field, the `promote_users` and
`manage_network_users` capability checks, the
`autocomplete_users_for_site_admins` filter, and large networks.

The handler bails out early unless multisite is active, so the class is
marked with the `ms-required` group.

Blank lines are added around the `$term` assignment to satisfy the
alignment sniff.

See #65051.

Co-Authored-By: Claude <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

This PR addresses unsafe handling of the term request parameter in the wp_ajax_autocomplete_user() Ajax handler by ensuring the value is unslashed and sanitized before being used in the get_users() search query, and adds PHPUnit coverage for the expected behavior.

Changes:

  • Sanitize $_REQUEST['term'] via wp_unslash() + sanitize_text_field() before building the get_users() search argument.
  • Add a dedicated Ajax test suite for the autocomplete-user action, covering expected results, permissions, large-network denial, and term sanitization/error behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/wp-admin/includes/ajax-actions.php Sanitizes the term request value before using it in the user query search string.
tests/phpunit/tests/ajax/wpAjaxAutocompleteUser.php Adds PHPUnit tests to verify autocomplete-user behavior, including sanitization and permission/denial cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/phpunit/tests/ajax/wpAjaxAutocompleteUser.php Outdated
Comment thread tests/phpunit/tests/ajax/wpAjaxAutocompleteUser.php Outdated
westonruter and others added 8 commits August 4, 2026 16:59
…e_user()

Note that the search term is now sanitized and that a missing, non-string,
or empty term results in a `0` response instead of an empty array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Since wildcards are appended to the search term, a term consisting only of
asterisks would be reduced to an empty search by WP_User_Query, causing all
network users to be returned. Trim asterisks before the empty-term check so
such a term short-circuits with a `0` response instead.

Add tests for an asterisk-only term and for an asterisk-wrapped term, the
latter of which still matches as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cover the remaining guard arms in wp_ajax_autocomplete_user(): an empty or
whitespace-only term and a non-string (array) term each result in a `0`
response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@westonruter westonruter changed the title $_REQUEST['term'] used unsanitized in user search query Users: Improve search term handling in wp_ajax_autocomplete_user() Aug 5, 2026
@westonruter westonruter changed the title Users: Improve search term handling in wp_ajax_autocomplete_user() Networks and Sites: Improve search term handling in wp_ajax_autocomplete_user() Aug 5, 2026
Convert the tag-stripping test to a data provider and add a case where tags
wrap the searchable value. The existing script-element case exercises
wp_strip_all_tags() removing an element along with its contents, while the
new case exercises tags being stripped with their inner text retained.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
pento pushed a commit that referenced this pull request Aug 5, 2026
In `wp_ajax_autocomplete_user()`, unslash and sanitize the `term` request parameter before it is passed to `get_users()`. Unslashing fixes searching for an email address containing an apostrophe (valid per `is_email()`), which could previously never match because `wp_magic_quotes()` added a slash which `wpdb::esc_like()` then escaped as a literal. Note that the raw term was already safely handled in the user query, since `WP_User_Query` passes the search term through `wpdb::prepare()`, so this is a hardening and correctness fix rather than a security fix.

Additionally, a missing, non-string, or empty term now short-circuits with a `0` response instead of returning an empty array, avoiding a PHP warning and needless user queries. Asterisks are also trimmed from the term given that wildcards are appended to it; a term consisting only of asterisks previously resulted in an empty search which matched all users on the network.

Also introduce the `Tests_Ajax_wpAjaxAutocompleteUser` test class covering the Ajax action's search behavior, input handling, and capability checks.

Developed in #11530.
Follow-up to r19897, r20279.

Props rajeshcp, wildworks, westonruter, liaison, gaurangsondagar, vgnavada, saadtajik.
Fixes #65051.


git-svn-id: https://develop.svn.wordpress.org/trunk@63016 602fd350-edb4-49c9-b593-d223f7449a82
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

A commit was made that fixes the Trac ticket referenced in the description of this pull request.

SVN changeset: 63016
GitHub commit: 7f81cb2

This PR will be closed, but please confirm the accuracy of this and reopen if there is more work to be done.

@github-actions github-actions Bot closed this Aug 5, 2026
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Aug 5, 2026
In `wp_ajax_autocomplete_user()`, unslash and sanitize the `term` request parameter before it is passed to `get_users()`. Unslashing fixes searching for an email address containing an apostrophe (valid per `is_email()`), which could previously never match because `wp_magic_quotes()` added a slash which `wpdb::esc_like()` then escaped as a literal. Note that the raw term was already safely handled in the user query, since `WP_User_Query` passes the search term through `wpdb::prepare()`, so this is a hardening and correctness fix rather than a security fix.

Additionally, a missing, non-string, or empty term now short-circuits with a `0` response instead of returning an empty array, avoiding a PHP warning and needless user queries. Asterisks are also trimmed from the term given that wildcards are appended to it; a term consisting only of asterisks previously resulted in an empty search which matched all users on the network.

Also introduce the `Tests_Ajax_wpAjaxAutocompleteUser` test class covering the Ajax action's search behavior, input handling, and capability checks.

Developed in WordPress/wordpress-develop#11530.
Follow-up to r19897, r20279.

Props rajeshcp, wildworks, westonruter, liaison, gaurangsondagar, vgnavada, saadtajik.
Fixes #65051.

Built from https://develop.svn.wordpress.org/trunk@63016


git-svn-id: http://core.svn.wordpress.org/trunk@62235 1a063a9b-81f0-0310-95a4-ce76da25c4cd
DanielHudson2 pushed a commit to DanielHudson2/wordpress-develop that referenced this pull request Aug 10, 2026
In `wp_ajax_autocomplete_user()`, unslash and sanitize the `term` request parameter before it is passed to `get_users()`. Unslashing fixes searching for an email address containing an apostrophe (valid per `is_email()`), which could previously never match because `wp_magic_quotes()` added a slash which `wpdb::esc_like()` then escaped as a literal. Note that the raw term was already safely handled in the user query, since `WP_User_Query` passes the search term through `wpdb::prepare()`, so this is a hardening and correctness fix rather than a security fix.

Additionally, a missing, non-string, or empty term now short-circuits with a `0` response instead of returning an empty array, avoiding a PHP warning and needless user queries. Asterisks are also trimmed from the term given that wildcards are appended to it; a term consisting only of asterisks previously resulted in an empty search which matched all users on the network.

Also introduce the `Tests_Ajax_wpAjaxAutocompleteUser` test class covering the Ajax action's search behavior, input handling, and capability checks.

Developed in WordPress#11530.
Follow-up to r19897, r20279.

Props rajeshcp, wildworks, westonruter, liaison, gaurangsondagar, vgnavada, saadtajik.
Fixes #65051.


git-svn-id: https://develop.svn.wordpress.org/trunk@63016 602fd350-edb4-49c9-b593-d223f7449a82
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants