One of my side projects runs on a self-hosted WordPress install, and this week its login page started getting hammered by a botnet. The interesting bit wasn’t the traffic. It was the username every bot was trying. It was my real sign-in name, spelled exactly right.
I asked an AI agent to work out where they got it, and to fix it without touching anything that could lock me out.
Where the login name was leaking
The agent started with logged-out requests from outside, the same view a bot gets. It found the leak in under a minute. WordPress builds author page addresses from a field called user_nicename, and when you create a user that field is just a copy of the login name. So my author archive lived at /author/<my-login>/.
That address showed up in three places:
- the author archive page itself,
- the SEO plugin’s author sitemap,
- the structured data (JSON-LD) on every single post, which links the author to that archive.
Anyone reading the page source or the sitemap had half of my credentials handed to them.
A snapshot before touching anything
Before proposing a fix, the agent saved a pre-check snapshot of how the site behaved, so we’d know exactly what changed later. Some of the usual holes were already closed:
- The REST API users endpoint returned 404, so no usernames there.
/?author=Nalready redirected to the homepage.- Unknown author pages were sent home by an anti-enumeration rule in my theme.
So the fix only had to deal with the slug itself. That’s a much smaller change than a “harden everything” pass, and smaller is easier to roll back.
Change the public slug, never the login
The obvious move is renaming the account. The agent ruled that out. Changing a login name on a live site is the kind of change that breaks sessions, saved passwords and anything else keyed to it. Instead it changed only the public slug and left the login alone. The author page moves to my display name, and the sign-in name stops appearing anywhere public.
It packaged the change as a tiny plugin, about a hundred lines, where activating it makes the change and deactivating it undoes it:
- On activation it renames the slug, but only if it’s still the old value and the new one isn’t used by another user. Otherwise it does nothing. It saves the old value in an option, clears the user cache, invalidates the sitemap cache and soft-flushes rewrites.
- While active it 301-redirects the old author URLs, including
/page/2/,/feed/and any query string, to the new slug, so old links and search results don’t break. - On deactivation it restores the saved slug and removes the redirect. That’s the rollback.
// Simplified. The real plugin also checks the slug isn't taken.
register_activation_hook( __FILE__, function () {
$user = get_userdata( AUTHOR_ID );
if ( ! $user || 'old-login' !== $user->user_nicename ) {
return; // already changed, or not the user we expect
}
update_option( 'author_slug_old', $user->user_nicename, false );
set_nicename( AUTHOR_ID, 'display-name' ); // user_login is never touched
} );
// Priority -10, so it runs before the theme's anti-enumeration redirect at 0.
add_action( 'template_redirect', 'redirect_old_author_urls', -10 );
The priority bug it caught before it shipped
This was my favourite catch. My theme’s anti-enumeration rule sends any unknown author page to the homepage. Once the slug changes, the old author URL is “unknown”. If the theme’s rule ran first, every old link would quietly land on the homepage instead of the new author page.
The agent read the theme’s hook priority and put the plugin’s redirect ahead of it. Then it tested the redirect logic offline, with a small stub that fakes the few WordPress functions it needs, against eight URLs. These were the old page, with and without a trailing slash, page 2, the feed, a query string, a near-miss slug with an extra character, the new slug, and a page with the same name outside the author path. The first five redirected correctly, and the last three didn’t redirect at all, which is what we wanted.
A runbook another agent can run
My agent can’t sign in to that site’s admin, and I don’t want it uploading plugins on its own anyway. So it wrote a runbook for a second agent that already has shell access to the server. The runbook has a pre-check and backup of the user row, the plugin file with its checksum and a syntax check, activation, a sitemap rebuild and cache purge, a verification list of exact URLs and expected responses, and a one-line rollback. It also included a changelog line to add once the change is live.
As I write this, the runbook hasn’t been run yet. The old author URL still answers, and the new one still redirects home. That’s fine. Nothing ships until the second agent’s verification output comes back and my agent checks it again from outside.
Ship checklist
- Check what a logged-out visitor sees: author archives, sitemaps, JSON-LD, the REST users endpoint and
?author=N. - Save a before snapshot of status codes and canonicals.
- Change the public slug, never the login name.
- Guard the change: only run if the current value is what you expect and the new slug is free.
- Make deactivation the rollback, and note that it is, so nobody “cleans it up” later.
- 301 every old variant (pages, feeds, query strings) to the new slug.
- Check hook priorities against existing redirects in your theme.
- Test the redirect logic offline, including near-miss URLs that must not match.
- Rebuild the sitemap and purge the page cache after the change.
- Verify from outside, logged out, and log the change with its rollback line.
FAQ
Isn’t hiding the username security through obscurity?
Partly, yes. Strong passwords, two-factor and rate limiting do the real work. But there’s no reason to publish half of your credentials in every post’s source code, and it costs nothing to stop.
Why a plugin instead of a one-off database update?
A database edit gets forgotten. A plugin with a clear name shows up in the admin, carries the redirects, and has rollback built in. Deactivating it puts everything back the way it was.
Will the slug change hurt SEO?
Not if the old URLs 301 to the new ones and the sitemap is rebuilt. Author archives are low-value pages anyway, and the redirect keeps any links pointing at them working.
Where does the AI actually help here?
It did the tedious checking: reading the theme’s hooks, spotting the priority clash, writing the guards, testing near-miss URLs and writing a runbook with exact expected outputs. I’d have shipped the rename and found the homepage redirect a week later.
Closest related Builders notes?
I Let an AI Agent Patch a Theme With a Tiny Reversible Plugin uses the same activate-to-apply, deactivate-to-undo pattern. I Had an AI Agent Audit My Self-Hosted Blog for Setup Leaks covers the other kind of leak, and I Gave My AI Agents One Shared Changelog With a Rollback Line is where the change gets logged.
