=== Stoutwall – Security Hardening & Login Protection ===
Contributors: felipepitol
Tags: security, hardening, limit login attempts, security headers, xml-rpc
Requires at least: 5.5
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.3.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Lightweight WordPress hardening with simple switches: limit login attempts, security headers, block PHP in uploads, disable XML-RPC and more.

== Description ==

Stoutwall applies well-known hardening measures that most sites should have, each behind its own switch. There is no firewall, no scanner and no account to create: it changes how WordPress behaves, and you can see and undo every change. One small plugin instead of five single-purpose ones.

**What it can do**

* **Limit login attempts** – after 5 failed logins in 15 minutes, the IP address is locked out for 15 minutes. Repeated lockouts last longer, up to 24 hours. Works behind Cloudflare and other proxies, with a list of allowed IP addresses and ranges that are never locked out.
* **Security headers** – sends `X-Content-Type-Options`, `X-Frame-Options` and `Referrer-Policy`, plus optional HSTS.
* **Prevent code execution in uploads** – adds rules to `wp-content/uploads/.htaccess` that block PHP files, including `.phtml`, `.php5`, `.phar` and double extensions such as `image.php.jpg`. Rules from other plugins in the same file are kept.
* **Disable XML-RPC** – turns off every XML-RPC method, including `pingback.ping`, which is often abused for DDoS amplification, and removes the `X-Pingback` header.
* **Prevent user enumeration** – stops `?author=N` lookups and hides usernames from the REST API for visitors, the users sitemap and oEmbed responses. Authors and editors keep the author list in the block editor.
* **Generic login errors** – the login form no longer tells attackers whether a username exists. Registration errors are still shown.
* **Disable application passwords** and **require login for the REST API** (both off by default).
* **Disable file editors** – removes the theme and plugin file editors from the dashboard.
* **Disable registration** – keeps "Anyone can register" off.
* **Block the "admin" username** for new registrations.
* **Hide username as display name** – users cannot save a profile whose public display name is their login.
* **Hide the WordPress version** from the page head and feeds.
* **Remove the X-Powered-By header** that reveals the PHP version.
* **Disable directory browsing** in the uploads folder.

**Check that it really works**

* A **Check status** button confirms that each protection is in effect on your server, for example that the `.htaccess` rules are present and the headers reach the browser, not only that the switch is on.
* A read-only **security audit** looks for weak security keys, copies of `wp-config.php`, a public `debug.log`, an "admin" user, open registration with a powerful role and more. It also tests from outside whether PHP files in uploads run and whether folders can be listed. The quick checks also appear in **Tools → Site Health**.

**Rules for your web server**

Stoutwall generates matching rules for **Apache, LiteSpeed, OpenLiteSpeed and Nginx**, so the protections also cover static files and servers that ignore `.htaccess`. You review and paste them; nothing outside the uploads folder is changed for you.

**For developers and agencies**

* WP-CLI: `wp stoutwall list|status|enable|disable|export|import|lockouts|unlock|allowlist|rules|audit`.
* Export the settings to a JSON file and import them on other sites.
* Filters such as `stoutwall_security_headers`, `stoutwall_public_rest_namespaces`, `stoutwall_login_max_failures` and `stoutwall_client_ip`.

**Privacy**

Stoutwall does not send anything to external servers, does not add cookies and does not send emails. To limit login attempts it keeps, on your own site, a hashed IP address of failed logins for 15 minutes and a partial IP address (such as 203.0.113.x) of locked-out visitors for up to a day.

**Reporting security issues**

Please report vulnerabilities privately through the [Wordfence](https://www.wordfence.com/threat-intel/bug-bounty-program/) or [Patchstack](https://patchstack.com/database/report) programs, which coordinate the fix with the author before disclosure.

== Installation ==

1. Install Stoutwall from **Plugins → Add New**, or upload the `stoutwall` folder to `/wp-content/plugins/`.
2. Activate the plugin. The protections that rarely cause problems start enabled.
3. Go to **Settings → Stoutwall** to turn protections on or off, then click **Check status**.
4. On Nginx or OpenLiteSpeed, open the **Server rules** tab and add the rules to your server.

== Frequently Asked Questions ==

= Does it work on Nginx and OpenLiteSpeed? =

Most protections work on any server. Blocking PHP in uploads relies on `.htaccess`, which Apache and LiteSpeed Enterprise read but Nginx and OpenLiteSpeed ignore. Open **Settings → Stoutwall → Server rules**, pick your server and paste the rules it shows. Then run the audit: it tests whether PHP in uploads is really blocked.

= I locked myself out. How do I get back in? =

Wait for the lockout to end, or run `wp stoutwall unlock --all` with WP-CLI. To avoid it, add your office or VPN address under **Settings → Stoutwall → Tools → Allowed IP addresses**. Without WP-CLI, rename the `wp-content/plugins/stoutwall` folder, log in, rename it back and unlock the IP in **Settings → Stoutwall → Tools**.

= Everyone gets locked out at the same time =

Your site is behind a proxy or CDN, so every visitor reaches WordPress with the same IP address. In the **Limit login attempts** row, open **Advanced** and choose the header your proxy sets, for example Cloudflare.

= Will it break my site? =

Each measure can be turned off on its own. Things to know before enabling them:

* Disabling XML-RPC stops the Jetpack connection and the legacy WordPress mobile apps.
* Security headers stop other sites from showing yours inside a frame. Turn them off if you need that.
* HSTS makes browsers refuse plain HTTP for a year. Only turn it on when HTTPS works on every page.
* Requiring login for the REST API can break themes and plugins that load content through it. oEmbed, Contact Form 7, WooCommerce checkout and Jetpack stay public; use the `stoutwall_public_rest_namespaces` filter for others.
* Disabling registration overrides the "Anyone can register" setting.
* Blocking PHP in uploads affects any plugin that executes PHP files stored in the uploads folder, which is rare and usually a red flag.

= What happens when I deactivate or delete the plugin? =

Deactivating removes the plugin's rules from the uploads `.htaccess` (other rules stay) and every other measure stops applying. Deleting the plugin also removes its settings.

= I used ProtectMyWordPress from GitHub. How do I move to Stoutwall? =

Install and activate Stoutwall. It imports your ProtectMyWordPress settings and replaces its `.htaccess` rules. Then deactivate and delete ProtectMyWordPress.

== Screenshots ==

1. Protections grouped by area, with a meter that shows how many are on.
2. The status check shows whether each protection is working.

== Changelog ==

= 1.3.0 =
* New: limit login attempts, with longer lockouts for repeat offenders, a list of locked-out IPs, allowed IPs and ranges, and support for Cloudflare and proxy headers.
* New: security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy) and optional HSTS.
* New: switches to disable application passwords and to require login for the REST API (both off by default).
* New: server rules for Apache, LiteSpeed, OpenLiteSpeed and Nginx, generated from the protections you have on.
* New: read-only security audit with live tests for PHP in uploads and directory listing, also shown in Site Health.
* New: WP-CLI commands and settings export/import.
* The X-Powered-By header is now also removed from the login page and the dashboard.
* Limit login attempts and security headers are turned on when you update. Turn them off in Settings → Stoutwall if you do not want them.

= 1.2.1 =
* The plugin link now points to the WordPress.org directory page.

= 1.2.0 =
* Renamed to Stoutwall for the WordPress.org Plugin Directory. Settings and `.htaccess` rules from ProtectMyWordPress are migrated on activation.
* Uploads: rules are written in their own block even when `uploads/.htaccess` already exists. Before, an existing file meant no rules were added while the status still reported OK.
* Uploads: rules work on Apache 2.2 and 2.4 and also cover `.phtml`, `.php5`, `.phar` and double extensions. They are removed on deactivation.
* XML-RPC: pingbacks are now disabled too, and the `X-Pingback` header is removed.
* User enumeration: also covers the users sitemap, oEmbed, POST requests and lists such as `?author=1,2`. The posts list filter in the dashboard and the block editor author list work again.
* Generic login errors no longer hide registration errors.
* "Hide username as display name" now checks profile updates. Before, it read a field that the registration form does not have, so it never applied.
* The status check reports the state of each measure, translated, with clear labels.
* All strings are in English and ready for translation.

= 1.1 =
* Fixed the settings form, the AJAX nonce and an XSS issue in notifications.
* Options are sanitized on save and assets load only on the settings page.

= 1.0 =
* First release, as ProtectMyWordPress.

== Upgrade Notice ==

= 1.3.0 =
Adds login attempt limits and security headers (both on after updating), server rules for Nginx and OpenLiteSpeed, a security audit and WP-CLI commands.

= 1.2.1 =
Updates the plugin link. No functional changes.

= 1.2.0 =
Fixes uploads protection on sites that already had an .htaccess file in uploads, and fully disables XML-RPC pingbacks. Recommended for all users.
