=== WPS Protect: Login URL & Security Headers === Contributors: junaid434 Tags: security, login, brute force, security headers, hide login Requires at least: 6.2 Tested up to: 7.1 Requires PHP: 7.2 Stable tag: 2.0.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Move your login URL, stop brute-force attacks, and send HTTP security headers on every response. Lightweight, cache-friendly, no bloat. == Description == **WPS Protect** combines the three defences most WordPress sites install three separate plugins for — a hidden login URL, brute-force rate limiting, and HTTP security headers — into one lightweight plugin with no upsells and no performance overhead. = Login URL protection = * Move `wp-login.php` to a slug of your choice. * Choose what the old login URL returns: a 404, your home page, a custom URL, or a plain message. * Slugs are validated before they are saved, against reserved WordPress paths, existing pages and post-type archives, so you cannot make your own content unreachable. * Signed-out visitors to `/wp-admin/` are sent to your login page with their destination preserved, instead of hitting a dead end. * AJAX, `admin-post.php` and REST endpoints stay reachable, so front-end forms, carts and the block editor keep working. * Password-protected posts, logout links, and password-reset emails all continue to work. = Emergency access = Losing your login URL should never mean editing the database. Three independent recovery routes: 1. A one-time rescue URL that works from any page on your site. 2. `define( 'WPSP_DISABLE_LOGIN_GATE', true );` in `wp-config.php`. 3. WP-CLI: `wp wpsp login-url`, `wp wpsp set-slug`, `wp wpsp disable-gate`. The site administrator is emailed whenever the rescue URL is used. = Brute-force protection = * Escalating lockouts — the second offence lasts longer than the first. * Tracks attempts per IP address **and** per username, so a distributed attack on one account is still caught. * Covers every way WordPress checks a password: the login form, XML-RPC, the REST API, Application Passwords, WooCommerce and BuddyPress. * `system.multicall` is disabled, closing the XML-RPC amplification trick that turns one request into hundreds of password guesses. * Requests to your old login URL count as attacks — nothing legitimate knows that address any more. * Proxy-aware: forwarding headers are trusted only from proxy ranges you configure, so nobody can spoof a header to dodge the limiter or get themselves allowlisted. * Optional honeypot field, and generic error messages so the login and lost-password forms stop confirming which accounts exist. * Locked-out requests return `429` with `Retry-After`, so Cloudflare, Fastly or fail2ban can shed the attack at the edge. = Security headers = * X-Frame-Options, X-Content-Type-Options, Referrer-Policy, X-XSS-Protection, X-Permitted-Cross-Domain-Policies, Strict-Transport-Security, Content-Security-Policy (including Report-Only), Permissions-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy and CORS. * Headers are sent on the front end, the login screen, the dashboard and REST responses — not just the front end. * Safe defaults: only headers that suit essentially every site are on out of the box. CSP, HSTS, Permissions-Policy and CORS ship switched off with a recommended value pre-filled, because guessing those breaks real sites. * Every value is validated against header injection before it is saved, and risky settings are flagged with an explanation rather than silently accepted. * HSTS is only sent over a secure connection and only after you confirm your subdomains are ready. = HTTPS enforcement = * Redirects insecure requests, honouring forwarding headers from trusted proxies so sites behind Cloudflare or a load balancer do not loop. * Form submissions redirect with `307`, so no data is lost. * Cannot be switched on from an insecure dashboard session, which is the usual way people lock themselves out. == Installation == 1. Upload the plugin files to `/wp-content/plugins/wps-protect-login-url-security-headers/`, or install through the Plugins screen. 2. Activate the plugin. 3. Go to **WPS Protect** in the dashboard menu. Activation does not change your login URL. Nothing moves until you set a slug yourself, and the plugin issues a rescue URL at that moment. == Frequently Asked Questions == = What happens if I forget my login URL? = Use the rescue URL shown when you saved the slug. If you no longer have it, add `define( 'WPSP_DISABLE_LOGIN_GATE', true );` to `wp-config.php`, or run `wp wpsp login-url` over WP-CLI. Deactivating the plugin also restores `wp-login.php`, and your settings survive deactivation. = Will this break my caching plugin? = Login and REST requests are never cached, so brute-force protection and the login gate are unaffected. Security headers are sent by PHP, so on a page served from a full-page cache they may be served by your cache layer instead. If your host serves cached pages without invoking PHP, set the headers at the server level too. = Does it work behind Cloudflare? = Yes, but configure your proxy ranges under the brute-force settings first. Until you do, the plugin deliberately declines to block by IP address rather than risk locking out every visitor sharing a proxy address. = Will it conflict with another security plugin? = Enable "do not overwrite headers already set" if something else is already sending headers. Running two plugins that both hide the login URL is not supported — pick one. = I upgraded from 1.x. What changed for me? = Your settings are migrated automatically, including your login slug, HTTPS setting and any header values you saved. One thing intentionally does not carry over: 1.x applied a hard-coded `Content-Security-Policy: default-src 'self'` whenever you had not set one, which blocked inline scripts and styles on most sites. That fallback is gone. If you want a CSP, set one explicitly on the Security Headers tab. == Screenshots == 1. Login Protection — custom slug, block behaviour, emergency access and active lockouts. 2. Security Headers — per-header toggles with validation and recommended values. 3. SSL — HTTPS enforcement with proxy-aware detection. == Changelog == = 2.0.0 = Complete architectural rewrite, plus WordPress 7.1 compatibility. **Fixed (critical)** * Administrators were locked out of `/wp-admin/` whenever a custom login URL was set. The capability check that guarded the dashboard was inverted: signed-in administrators were blocked while lower-privileged users were let through. * Every `admin-ajax.php` request returned 404 with a custom login URL set, breaking the block editor, media uploads, WooCommerce and any plugin using AJAX. * The default `Content-Security-Policy: default-src 'self'` was applied to every site that had not visited the settings screen, blocking inline scripts and styles used by WordPress core, most themes and most plugins. * `Access-Control-Allow-Origin: *` is no longer sent by default. * The HTTPS redirect built its target from the unvalidated `Host` request header, allowing an open redirect, and issued it as a permanently cached `301`. * Requests were matched with an unanchored substring search, so any URL merely containing `wp-admin` or `wp-login.php` — including ordinary posts and uploads — returned 404. * `exit` inside the `wp_logout` action aborted password resets and any programmatic logout. * Password-protected posts (`action=postpass`) no longer return 404. * Settings are no longer deleted when the plugin is deactivated. Deletion now happens on uninstall, as it should. **Added** * Brute-force protection: escalating lockouts, per-IP and per-username tracking, allowlist and blocklist, honeypot, generic error messages, and `429` with `Retry-After`. Covers the login form, XML-RPC, REST, Application Passwords, WooCommerce and BuddyPress. * Emergency access: one-time rescue URL, a `wp-config.php` constant, and WP-CLI commands. * Login slug validation against reserved paths, existing content and post-type archives. * Security headers now apply to the login screen, dashboard and REST API, not only the front end. * Content-Security-Policy-Report-Only, Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy and X-Permitted-Cross-Domain-Policies. * Header value validation that rejects response splitting, with advisory warnings for risky values. * Proxy-aware client IP resolution with configurable trusted ranges, and proxy-aware HTTPS detection. * WP-CLI: `wp wpsp login-url`, `set-slug`, `disable-gate`, `rescue`, `unlock`, `lockouts`, `audit`. * Audit log with retention policy and an IP anonymisation option. **Changed** * Rewritten as PSR-4 classes under `src/`, separated into Login, Headers, SSL, Brute-force, Admin, Support, CLI and Compat modules. * Settings are saved through `admin-post.php` with capability checks, nonce verification and a POST/Redirect/GET cycle, so refreshing no longer resubmits. * `X-XSS-Protection` now defaults to `0`, matching current OWASP guidance. The previous `1; mode=block` enabled a deprecated browser filter with vulnerabilities of its own. * HSTS requires an explicit confirmation and a secure connection before it is sent. * Minimum requirements raised to WordPress 6.2 and PHP 7.2. WordPress 6.2 is required for the `%i` identifier placeholder in `$wpdb->prepare()`, which the brute-force log uses to quote table names. **Compatibility** * Tested against WordPress 7.1. The plugin ships no JavaScript or CSS, so the 7.1 iframed editor, `@wordpress/components`, jQuery UI 1.14.2 and client-side media processing changes do not apply to it. Applying security headers across `wp-admin` is a separate, off-by-default option, because a restrictive Content-Security-Policy can interfere with the now always-iframed editor. * The `wpsplu_options` record, the `wpsplu` admin page slug and its tab URLs are all preserved. Removed 1.x functions remain declared as no-op shims that raise a deprecation notice rather than a fatal error. = 1.1 = * Added Permissions-Policy header support * Improved Content-Security-Policy configuration * Enhanced login URL handling * Added tab-based admin interface * Updated security headers with recommended values = 1.0 = * Initial release with basic security features == Upgrade Notice == = 2.0.0 = Fixes a lockout that blocked administrators from wp-admin and broke all AJAX when a custom login URL was set, and drops a default CSP that broke inline scripts and styles. Adds brute-force protection and emergency login recovery. Settings migrate automatically. Needs PHP 7.2.