Stop the WordPress REST API listing user names
The users endpoint answers anonymous requests with account slugs, which are usually the login names.
Written by Hamza Ahmad Aslam. Updated . One of the what it leaks guides.
What the checker sees
It asks for /wp-json/wp/v2/users?per_page=1 as a logged-out visitor. If the answer is JSON with a slug field, the finding is raised and the report quotes the first slug it saw.
Why it matters
WordPress lists every user who has published a post. The slug is made from the login name when the account is created, so on most sites it is the login name. A password-guessing attack needs a login and a password; this hands over the first half.
Login names are not secrets, and hiding them is a small step. Strong passwords, two-factor login and a limit on login attempts do far more. Close the endpoint anyway: it costs nothing.
Close the endpoint to visitors
Most security plugins have a setting for this. Without a plugin, add a must-use plugin such as wp-content/mu-plugins/hide-users.php:
<?php
// Remove the user endpoints for anyone who is not logged in.
add_filter( 'rest_endpoints', function ( $endpoints ) {
if ( is_user_logged_in() ) {
return $endpoints;
}
unset( $endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
return $endpoints;
} );Logged-in editors still get the endpoint, which the block editor needs for its author list.
The same names in two other places
The checker only tests the REST API, but author archives (/?author=1 redirects to /author/<slug>/) and the core sitemap (/wp-sitemap-users-1.xml) show the same slugs. To drop the users sitemap:
add_filter( 'wp_sitemaps_add_provider', function ( $provider, $name ) {
return 'users' === $name ? false : $provider;
}, 10, 2 );Check the fix
curl -s https://example.com/wp-json/wp/v2/usersWith the snippet above you get a 404 with "code":"rest_no_route". A security plugin may answer 401 or 403 instead; any of them passes.
Check a site for this
Other fixes
- Hide the WordPress version numberThe version shows in the page's generator tag and in the stock readme.html. Both can go in a few minutes.
- Turn off XML-RPC in WordPressxmlrpc.php still answers. Unless Jetpack or a publishing app needs it, block it at the server.
- The WordPress security headers the checker readsFive response headers and HTTPS itself. Set them once at the server or CDN and every page carries them.