Ports
WordPress REST API user enumeration: what it leaks, and the exact fix.
Published 2026-06-08 · Last updated 2026-06-08 · Vantyris editorial
Visit /wp-json/wp/v2/users on most WordPress sites and you'll get back a JSON array with the public slug of every account that has authored a post. Those slugs are usually the login names. Attackers feed them into credential-stuffing and password-spray tools that hammer /wp-login.php. The endpoint is meant to be public (WordPress uses it to render author archives), but a small-business site rarely needs that, and locking it down is a one-filter PHP edit.
What this means for your business
- The endpoint returns the slug, display name, and ID of every published-content author. The slug is usually the username, or close to it. With
/wp-login.phppublicly reachable as well, an automated attacker gets a clean target list and no longer has to guess usernames before guessing passwords. - It does NOT expose hashed passwords, email addresses, or accounts that haven't authored content. Subscriber-only or admin-only accounts that haven't published a post don't appear. The risk is reconnaissance, not direct compromise.
- Most modern WordPress brute-force botnets check
/wp-json/wp/v2/usersfirst, because it's faster and quieter than the classic?author=1enumeration loop. Closing the endpoint moves your site off the easy-target list without breaking the front-end.
How to fix
Add a rest_endpoints filter that unsets the /wp/v2/users endpoint for unauthenticated requests. It's a five-line snippet in your theme's functions.php or in a small must-use plugin.
- Decide where to put the code. If you have a child theme, use its
functions.php, which survives core theme updates. Otherwise create a tiny must-use plugin atwp-content/mu-plugins/disable-user-rest.php, containing a<?phpopener and the snippet. Must-use plugins survive theme changes too. - Drop in the filter. Paste the following. The
unseton the regex version handles requests like/wp/v2/users/1. Without it, attackers can still walk users by ID.add_filter('rest_endpoints', function ($endpoints) { if (isset($endpoints['/wp/v2/users'])) { unset($endpoints['/wp/v2/users']); } if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) { unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']); } return $endpoints; }); - Verify the fix. Open
https://your-site/wp-json/wp/v2/usersin an incognito browser. You should get a 404 or 401 response, not a JSON array. Then tryhttps://your-site/?author=1. If your site still 301-redirects to/author/<slug>/, you also need to block the classic enumeration path (see the gotcha below). - Re-scan to confirm. Run a Vantyris scan after the change. The
wp.rest_users_endpoint_protectedhealthy signal should appear. If the finding still shows aswp.rest_user_enum, something else is exposing users. Check Wordfence, or a cache layer that might be serving a stale 200 from before the change.
Owner: Your web developer or whoever maintains the WordPress theme. · Time: About 30 minutes including verification.
Common gotchas
- If you actually use author archives on the front-end (most blogs do), this endpoint also powers them through the admin-side. The fix above only unsets the public REST route, not the admin-side query, so author archives keep working. Test the archives after the change.
- Also block
?author=1enumeration. WordPress's default behaviour is to 301-redirect/?author=Nto/author/<slug>/, which leaks the same data the REST endpoint did. Wordfence and iThemes Security have one-click toggles for this. The manual route is a rewrite rule that returns 404 for any URL withauthor=in the query string. - If you run a custom plugin that talks to
/wp/v2/usersover an authenticated REST connection, the filter above breaks it. Userest_authentication_errorsinstead, to allow the endpoint only for logged-in users. - Caching layers (Cloudflare, WP Rocket, LiteSpeed Cache) may serve a stale 200 for the old endpoint after the change. Purge the cache to confirm the lockdown took effect.
How to verify the fix
From a browser in incognito mode (no cookies, not logged in), open /wp-json/wp/v2/users. The healthy result is 401, 403, or 404, meaning anything other than a JSON array of user objects. Also confirm /wp-json/wp/v2/users/1, /wp-json/wp/v2/users/2, and /?author=1 all return non-200 responses. Re-running a Vantyris scan records the closure as an audit-trail row.
Cyber Essentials alignment
This finding informs the following Cyber Essentials control areas (the UK government's baseline scheme, a sound checklist in any country):
- A4. User access control: accounts assigned the minimum required privileges.
Vantyris is not a CE certifying body. The mapping above is informational.
Common follow-up questions
Does this break anything on my front-end?
Almost never. Author archives render from server-side queries, not from the REST API. Subscription forms, contact forms, and most page-builder plugins don't touch this endpoint either. If you have a custom React or single-page front-end that authenticates to WordPress and reads users, that integration breaks. Switch it to rest_authentication_errors so authenticated reads still work.
Should I just install Wordfence and skip the code edit?
Wordfence (free tier) has a one-click toggle that does essentially the same thing. If you already run Wordfence, use it. That's the faster path. The code version above is for sites that don't want another full plugin for this one fix.
Will this stop brute-force attacks against /wp-login.php?
It removes the source of the username list. It doesn't stop the brute-forcing itself. For that you need rate-limiting (Wordfence, iThemes), a WAF rule on /wp-login.php, and account-level 2FA. Locking down the endpoint is one layer of defence. The others sit on the login page itself.
Why isn't this disabled by default in WordPress?
The endpoint is part of the REST API contract, and WordPress's headless / Gutenberg / Jetpack functionality assumes it's there. Disabling it by default would break too much. The core team's position is that exposing public author slugs isn't a vulnerability. Whether to treat it as recon is the site owner's call.
References
- WordPress REST API: Users endpoint Vendor
- OWASP: Username enumeration OWASP
- NCSC: Password administration for system owners NCSC
Related explainers
- HSTS: the security header that locks HTTPS on for good.
- What a passive security scan can and cannot prove about your site.
Want Vantyris to check your domain for this and 196 other problems?
The teaser scan is free and needs no card. A verified scan starts at $10€10£10A$15¥1,500AED 40 and comes with the workspace: finding workflow, score trend, three PDF layouts, share links and monitoring.
Written by Vantyris