WP Front Door

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 . 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
<?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:

PHP
add_filter( 'wp_sitemaps_add_provider', function ( $provider, $name ) {
	return 'users' === $name ? false : $provider;
}, 10, 2 );

Check the fix

Shell
curl -s https://example.com/wp-json/wp/v2/users

With 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

A domain or a full address. Only public pages are fetched, the way a browser would.