WP Manifestindependent plugin directory
manifest / security / wp-mu-security

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.

by nitida · github.com/lintmycode/wp-mu-security

0stars
0forks

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.zip

Readme

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.

Read the full README on GitHub →