WordPress user enumeration: why /?author=1 hands attackers half a login
A login has two halves: a username and a password. User enumeration gives an attacker the first half for free — and WordPress leaks it in two well-known places unless you stop it.
Vigil flagged this because your site confirmed a valid account name to an anonymous request. That’s user enumeration: the site telling a stranger “yes, this username is real.” On its own it isn’t a breach — but it removes the single biggest obstacle to a brute-force or credential-stuffing attack, which is not knowing who to attack.
What Vigil detected
Two probes confirm enumeration. The first requests /?author=1: a default WordPress install answers with a 301/302 redirect to /author/<login-slug>/, quietly revealing the account’s username. The second requests the REST endpoint /wp-json/wp/v2/users, which on many installs returns a JSON array of every author — names, slugs and IDs — with no authentication at all. Vigil records only the COUNT of accounts it could enumerate, never the names themselves.
Why it matters
Attackers automate this. A bot scrapes your author slugs, then points a password-spraying tool at wp-login.php or xmlrpc.php using those exact usernames. Real usernames turn a hopeless guessing game into a targeted one — especially dangerous when an admin’s display name doubles as their login, or when the same person reuses a password that’s already in a breach corpus.
You can’t brute-force an account you can’t name. Enumeration is what gives the bot its target list.— the RD-10 design note
How to fix it
Close both doors. Block the author-scan redirect at the web server, and disable the unauthenticated users REST endpoint. Then make sure no login name is guessable from a public display name.
# nginx — block ?author=<n> scans
if ($args ~* "author=\d+") { return 403; }
# Apache / .htaccess equivalent
RewriteCond %{QUERY_STRING} (^|&)author=([0-9]+) [NC]
RewriteRule ^ - [F]// Hide the users REST endpoint from anonymous callers (functions.php or an mu-plugin)
add_filter('rest_endpoints', function ($endpoints) {
unset($endpoints['/wp/v2/users']);
unset($endpoints['/wp/v2/users/(?P<id>[\\d]+)']);
return $endpoints;
});Finally, give each admin a display name that is different from their login, and put a rate-limit or a WAF rule in front of wp-login.php. A reputable security plugin (Wordfence, Solid Security) bundles all of the above, but the two snippets here fix the leak Vigil actually detected. Re-run the probe afterwards — /?author=1 should return 403 and the users endpoint should 401.
Our team can apply this fix on your site and verify it with Vigil. Already monitoring this site with Vigil? Open the failing check and hit “Request a fix” — we’ll get the exact problem and URL. Otherwise, get in touch.