"Stop user enumeration" blocks Application Passwords: the built-in exception regex requires digits, but WordPress core uses /users/me/

Summary

With Stop user enumeration enabled, WordPress Application Passwords cannot be
created at all. The plugin already contains an exception intended to keep that
route open, but the exception never matches, because it requires a numeric user
ID while WordPress core uses the literal me.

The result is a silent dead end: the authorize screen appears, the user clicks
“Yes, I approve of this connection”, and the request is blocked with the generic
“Request not allowed” page. Nothing in that page or in the log names the setting
responsible.

The mismatch

cerber-common.php:1837 — the exception:

if ( preg_match( '#^wp/v\d+/users/\d+/application-passwords#', $rest_path ) ) {
    return true;
}

\d+ requires digits. But WordPress core registers the route to accept either
form — wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php:26:

$this->rest_base = 'users/(?P<user_id>(?:[\d]+|me))/application-passwords';

and core’s own authorize screen hardcodes the me variant —
wp-admin/js/auth-app.js:57:

path: '/wp/v2/users/me/application-passwords?_locale=user',

So the exception is written for a form that the WordPress admin UI never sends.

Execution then falls through to the user-enumeration block at
cerber-common.php:1844:

if ( ! empty( $opt['norestuser'] ) ) {
    $path = explode( '/', $rest_path );
    if ( $path && count( $path ) > 2 && $path[0] == 'wp' && $path[2] == 'users' ) {
        // ... is_super_admin() / norestuser_roles checks ...
        CRB_Globals::set_ctrl_setting( 'norestuser' );
        return false;
    }
}

$path[2] === 'users' matches, and for any non-super-admin whose role is not in
norestuser_roles the request is denied. Note that “Allow logged-in users”
(restauth) is evaluated later
in the same function and therefore cannot
rescue the request — a logged-in user with a valid session is blocked all the
same.

Steps to Reproduce

  1. Settings → Hardening: enable Stop user enumeration, leave “Allow access to
    users’ data via REST API for these roles” empty.
  2. Settings → Users: set Application Passwords to Enabled (either value).
  3. Log in as a non-administrator (any role) and open
    /wp-admin/authorize-application.php?app_name=Test.
  4. Click “Yes, I approve of this connection”.

Expected: the application password is created — the exception at line 1837
exists precisely to allow this.

Actual: POST /wp-json/wp/v2/users/me/application-passwords403, Cerber’s
“Request not allowed” page. Reproduced five times in our traffic log, all with
http_code 403.

Administrators are unaffected because of the is_super_admin() branch, so the
issue is easy to miss when testing with an admin account.

Suggested Fix

Accept the form core actually uses:

if ( preg_match( '#^wp/v\d+/users/(?:\d+|me)/application-passwords#', $rest_path ) ) {
    return true;
}

That mirrors core’s own route definition and keeps the original intent intact.

A second, smaller point

The block page names neither the rule nor the setting:

Es ist leider nicht erlaubt, fortzufahren. Die Anfrage ähnelt verdächtig
automatisierten Anfragen von Spam-Posting-Software oder wurde durch eine vom
Website-Administrator konfigurierte Sicherheitsrichtlinie blockiert.
RID: GRR3ND4FWSXLKFAISZJQC15D

The RID does lead to the matching row in the Traffic Inspector, which is useful —
but an administrator has to know that mechanism exists before it helps. Surfacing
the responsible setting (here: norestuser) in the log detail column, the way
restwhite and restauth already appear elsewhere, would shorten diagnosis
considerably. We spent several hours on this, wrongly suspecting two other
components first, before a debug_backtrace() on wp_redirect pointed at the
right place.

Environment

WP Cerber Security 9.9.3 (free edition)
WordPress 7.0.4
PHP 8.4
Server Apache, shared hosting

Relevant settings: norestuser = 1, norest = 1, restauth = 1,
app_pwd = 2, norestuser_roles initially empty.

Workaround we are using

Adding the affected roles to “Allow access to users’ data via REST API for
these roles”
. That works, but it is broader than necessary — it opens the whole
wp/v2/users/* namespace for those roles just to permit one sub-route that the
plugin already intends to allow.

Thanks for reporting this. We’re on it!

Thanks for reporting this. We have confirmed the issue, but it is narrower than it first appears: the Authorize Application flow is blocked for non-privileged users when Stop user enumeration is enabled.

The issue occurs when a logged-in user who is not exempted by the configured role rules attempts to create an application password through WordPress’s standard authorize application flow (authorize-application.php).

In this flow, WordPress uses the REST endpoint /wp/v2/users/me/application-passwords, which WP Cerber currently blocks as part of the user-enumeration protection.

The issue does not affect all application password workflows. In particular, creating an application password directly from the WordPress user profile using the numeric user ID is not affected.

The fix will be included in the next release.