nitida — WP Security Hardening
WordPress must-use plugin: closes the default anonymous surface (XML-RPC, user enumeration, fingerprints, comments, login errors, application passwords). Same file on every site nitida runs.
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/lintmycode/wp-mu-security/archive/refs/heads/main.zipReadme
nitida/wp-mu-security
WordPress must-use plugin, installed with Composer, that closes the anonymous surface every WordPress site leaks by default. One file, identical on every site we run, on any host. Bedrock's autoloader loads it; there is nothing to activate.
What it does
| # | Measure | Why |
|---|---|---|
| 1 | XML-RPC off, X-Pingback header removed |
brute-force amplifier (system.multicall), pingback DDoS relay |
| 2 | generator, RSD, wlwmanifest and shortlink stripped from <head> and feeds |
exact-version fingerprinting |
| 3 | Comments and pings closed everywhere, admin menu hidden | unused; the comment form is the biggest anonymous write surface |
| 4 | User enumeration blocked for anonymous visitors: /wp/v2/users → 404, ?author=N → 404 (not a 301 to /author/<login>/), author archives → 404, oEmbed without author_* |
every login name handed out for free otherwise |
| 5 | No canonical redirect on 404 | stops WordPress "guessing" /wp/wp-login → login page and partial slugs → posts |
| 6 | One generic login error | WordPress says which half of the credentials was right |
| 7 | Application passwords off | second, rarely watched login surface |
Everything that depends on the visitor is scoped to logged-out requests. Editors, shop managers and REST clients with a login see no difference.
Opt-outs
Set in the site's .env (or Config::define() in config/application.php):
WP_MU_SECURITY_ALLOW_COMMENTS=true # a blog that really takes comments
WP_MU_SECURITY_ALLOW_APP_PASSWORDS=true # an integration authenticates with them
WP_MU_SECURITY_ALLOW_XMLRPC=true # Jetpack / the mobile app
There are no other switches on purpose.
Install (Bedrock)
composer config repositories.wp-mu-security vcs https://github.com/lintmycode/wp-mu-security.git
composer require nitida/wp-mu-security:^1.0
composer/installers puts it in web/app/mu-plugins/wp-mu-security/. Make
sure that directory is ignored (web/app/mu-plugins/*/ or the folder itself)
and that any hand-copied security-hardening.php in mu-plugins/ is deleted,
so the hooks are not registered twice.
After deploying, purge the page cache: <head> changes are cached HTML.
Verify, anonymously
H=https://example.pt
curl -s -o /dev/null -w '%{http_code}\n' $H/wp-json/wp/v2/users # 404
curl -s -o /dev/null -w '%{http_code}\n' "$H/?author=1" # 404, not 301
curl -s -o /dev/null -w '%{http_code}\n' $H/author/admin/ # 404
curl -s "$H/wp-json/oembed/1.0/embed?url=$H/" | grep -c author_name # 0
curl -s -o /dev/null -w '%{http_code}\n' -X POST $H/xmlrpc.php # 403/405/404, never 200 with a method list
curl -s $H/ | grep -c 'name="generator"' # 0
Not this plugin's job
Rate limiting wp-login.php and xmlrpc.php, the WAF / Cloudflare-only
origin, two-factor enforcement, backups and updates are host-level concerns.
On our own box they are nginx limit_req + fail2ban, a shared snippet, and
the monthly update round.