Volver al blog
7 min read
WordPress Security

WordPress hands your usernames to attackers. Here's how to shut the door.

A field report after a targeted campaign: why WordPress exposes your logins by default, and an open-source (MIT) plugin to stop it.

Key Takeaways
  • Brute force needs a username AND a password — WordPress gives the username away for free through four default channels.
  • ?author enumeration, the REST users endpoint, the lost-password form and login error messages all confirm which accounts exist.
  • A lightweight open-source plugin closes all four, with no settings, no database tables, and no measurable performance cost.

In April 2026, a production WordPress site I maintain was hit by a targeted attack campaign — thousands of coordinated attempts over several days, not a harmless automated sweep.

Going through the logs, the brute force itself wasn't the interesting part. It was this: the attacker never had to guess the login usernames. WordPress handed them over.

A brute-force attack needs two ingredients: a username and a password. We obsess over the second one (strong passwords, 2FA, fail2ban) and ignore the first — even though WordPress broadcasts it by default, through several channels.

WordPress enumeration requests blocked: ?author returns 404, the REST users endpoint returns 401, lost-password rate-limited

Why WordPress leaks your usernames by default

You can test every one of these on your own site right now. Each is a free hint handed to an attacker before any password is ever tried:

  1. Author enumeration: open /?author=1. If it redirects to /author/some-username/, a valid login just leaked. Iterate ?author=2, ?author=3 and you harvest every account.
  2. REST API user listing: /wp-json/wp/v2/users returns the list of accounts — username and slug included — to any anonymous visitor.
  3. Lost-password form: it confirms whether an account exists (a different message per case) and sends a real email on every submission, making it both an information leak and a harassment vector.
  4. Login error messages: "unknown username" versus "incorrect password" is enough to confirm which logins are real.

The common thread is simple: you cannot brute-force a username you never found. Close these four doors and you remove the reconnaissance step that makes the whole attack chain possible.

Hiding the username doesn't fix a weak password — but it removes the shortcut. The attacker has to find the door before they can pick the lock.

What the plugin does

I wrote a small plugin that closes exactly these four vectors. No settings page, no options screen, no database tables — only 15-minute transients for the rate limit:

  1. /?author=N returns a hard 404 instead of redirecting to the author archive.
  2. /wp-json/wp/v2/users returns 401 for anonymous visitors, while staying available to logged-in users.
  3. The lost-password form is capped at 5 attempts per 15 minutes per IP, with an identical response whether or not the account exists.
  4. Login errors collapse to a single generic message, so they no longer confirm valid usernames.

It runs on single-site and multisite, WordPress 5.x and up, PHP 7.4 and up. You activate it and it protects — there is nothing to configure.

The results

On a single site, after deployment, over 23 days:

  1. 36,127 enumeration attempts blocked.
  2. 4,842 distinct IP addresses involved.
  3. 0 usernames exposed through the four vectors above.
The plugin runs today on around thirty production sites across three hosting providers — the same hardening I apply to every site I maintain.

What this plugin does NOT do

Selling false peace of mind is worse than no plugin at all, so here is the honest scope:

  1. It does not rate-limit wp-login.php itself. High-volume brute force against the login page belongs at the server level (fail2ban, WAF). This closes the reconnaissance step that comes before it.
  2. Behind a proxy or CDN (Cloudflare and similar), REMOTE_ADDR is the proxy IP, so the lost-password rate limit becomes global instead of per-visitor until you restore the real client IP server-side.
  3. Blocking the REST users endpoint will break headless setups, or plugins that rely on anonymous access to that endpoint.
This is not a security suite. It is one focused building block that does a single thing — close username enumeration — and does it well.

Installing it in two minutes

The plugin is published under the MIT license. Two ways to install it:

  1. From the WordPress admin: download the ZIP, upload it under Plugins > Add New, and activate. On activation it installs itself as a must-use plugin, so it cannot be switched off from the admin — even by a compromised account.
  2. Manually: copy the single mu-plugin file into wp-content/mu-plugins/ — no activation needed.

View the source on GitHub

Read the code, audit it, fork it. It is short enough to review in a few minutes — which is exactly what you should expect from anything that touches your login flow.

Close the door before someone tries the handle

Username enumeration isn't a sophisticated attack — it's low-hanging fruit that WordPress leaves in plain sight. The version, the author archives, the REST API, the password form: all of them quietly confirm who can log in.

Closing them takes one lightweight plugin and zero configuration. If you manage WordPress sites, it's a five-minute change that removes the very first step every brute-force campaign relies on.

Is your WordPress site leaking usernames?
Run a free Orilyt audit and see whether author enumeration, the REST API or version exposure are giving attackers a head start — alongside 56 other security and performance tests.
Launch a free audit