=== Login Delay Shield === Contributors: michael.damoiseau Donate link: http://damoiseau.me/ Tags: security,login,brute-force,lockout,xmlrpc,authentication,anti-spam,password-protection Requires PHP: 7.4 Requires at least: 3.5.1 Tested up to: 7.0 Version: 2.4.1 Stable tag: 2.4.1 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Login Delay Shield slows down brute-force attacks by adding a configurable delay to failed login attempts while keeping successful logins instant. == Description == WordPress is one of the most widely used content management systems on the internet, making it a frequent target for bots and hackers attempting brute-force attacks. A brute-force attack works by systematically trying passwords until finding the correct one. Login Delay Shield defends against this by adding a configurable delay after each failed login attempt. Since successful logins are never delayed, legitimate users experience no slowdown. This approach is particularly effective against bots that send thousands of login requests, as each failed attempt forces the attacker to wait before trying the next password. **Features:** * **Security Setup Wizard** — Choose Conservative, Balanced, or Aggressive protection profiles from the settings page * **Login delay** — Fixed or random delay on failed login attempts (1-10 seconds) * **Progressive delay** — Delay increases with each consecutive failed attempt from the same IP * **IP lockout** — Temporarily block IP addresses after too many failed attempts * **Username-aware lockout strategy** — Choose `IP only` or `IP + username` to reduce false positives on shared networks * **Login feedback** — Shows remaining attempts before lockout and a lockout countdown when blocked * **IP whitelist** — Bypass all security measures for trusted IPs (supports CIDR notation) * **Email notifications** — Receive alerts when failed login thresholds are reached * **Failed login log** — Track all failed attempts with a dashboard widget showing recent activity, 7-day trends, and top targeted usernames * **fail2ban logging (optional)** — Write fail2ban-compatible failed-login and lockout lines to a safe log file * **XML-RPC protection** — Apply delays to XML-RPC authentication or block it entirely * **Password reset protection** — Apply delays, lockouts, and logging to password reset submissions without revealing account existence * **Custom login URL** — Move the login page to a custom URL to reduce automated bot traffic targeting `/wp-login.php` * **Log retention** — Automatic cleanup of old log entries (configurable retention period) * **Accessible admin interface** — WCAG 2.1 compliant with keyboard navigation and screen reader support * **Multilingual** — Translated into 18 languages including French, German, Spanish, Japanese, Chinese, Arabic, and more * Lightweight and compatible with other security plugins **Free means free** Login Delay Shield has no ads, no upsells, no premium tier, and no account or API key requirement. Every admin notice is dismissible, and the plugin never nags you to upgrade — there is nothing to upgrade to. **You can always get back in** A security plugin that locks out its own administrator is worse than no security at all. Login Delay Shield is built so an admin can always recover access: * Whitelisted IPs (including CIDR ranges) bypass every delay and lockout * The Active Lockouts manager on the settings page lists current lockouts with a one-click Unlock for each, plus an "Unlock Current IP" action * WP-CLI recovery commands: `wp login-delay-shield unlock-ip ` and `wp login-delay-shield flush-lockouts` * Lockouts are always temporary (24 hours maximum) — there are no permanent bans *This plugin is not a complete security solution — dedicated security plugins offer more comprehensive protection.* However, Login Delay Shield adds an effective layer of defense that works alongside your existing security measures without conflict. *Note: This plugin was formerly known as "WP Login Delay".* == Installation == 1. Upload the `wp-login-delay` folder to the `/wp-content/plugins/` directory 1. Activate the plugin through the 'Plugins' menu in WordPress 1. That's it, Login Delay Shield is installed and working == Frequently Asked Questions == = How does this plugin protect my site? = When a bot attempts a brute-force attack, it tries thousands of passwords as fast as possible. By adding a delay (even just 1 second) after each failed attempt, the attack becomes impractical. A one-second delay is barely noticeable to legitimate users but makes a huge difference when multiplied across thousands of attempts. = Where are the plugin settings? = Go to `Settings` > `Login Delay Shield` = What are protection profiles? = Protection profiles are guided presets in the Security Setup Wizard. Applying a profile updates the main delay, progressive delay, lockout, email alert, and authentication endpoint settings, while still leaving every individual control editable. = What is progressive delay? = Progressive delay increases the wait time with each consecutive failed attempt from the same IP address. For example, the first failure might delay 1 second, the second failure 2 seconds, and so on. This makes repeated attacks increasingly slow. = How does IP lockout work? = After a configurable number of failed attempts (default: 10), login attempts are temporarily blocked. You can choose whether attempts are counted by `IP only` or by `IP + username` (recommended for shared office/mobile IPs). Lockout duration is configurable (default: 60 minutes). = What are the "attempts remaining" and countdown messages? = When lockout is enabled, failed logins show how many attempts remain before temporary lockout. If lockout is triggered, the error message includes a countdown (for example, "try again in 2 minutes") so users know when to retry. = How do I whitelist my own IP? = Enable the IP whitelist feature and add your IP address (or a range using CIDR notation like `192.168.1.0/24`). Whitelisted IPs bypass all delays and lockouts, ensuring you never lock yourself out. = What happens if I lock myself out? = You can always get back in. Lockouts are temporary by design (24 hours maximum — there are no permanent bans), so waiting always works. To recover immediately: * If another administrator can log in, the Active Lockouts manager on the settings page shows every current lockout with a one-click Unlock, and an "Unlock Current IP" action. * With shell access, use WP-CLI: `wp login-delay-shield unlock-ip ` or `wp login-delay-shield flush-lockouts`. * With only FTP access, add `define( 'WLDELAY_SAFE_MODE', true );` to `wp-config.php` — this safe mode disables all delays and lockouts until you remove the line (a warning shows in the admin while it is active). * To avoid lockouts entirely, whitelist your own IP (CIDR ranges supported) — whitelisted IPs bypass all delays and lockouts. = Is there a premium version? Will I see ads or upsells? = No. Login Delay Shield is completely free: no ads, no upsells, no premium tier, no account, and no API keys. Every admin notice is dismissible. = What if my custom login URL stops working? = Some plugins that move the login page can lock you out behind a 404 with no way back. Login Delay Shield ships an emergency bypass: add `define( 'WLDELAY_DISABLE_CUSTOM_LOGIN', true );` to `wp-config.php` and the standard `wp-login.php` works again immediately — no need to disable the plugin. The custom slug also uses raw path matching, so it keeps working even with stale rewrite rules or plain permalinks. = I use Cloudflare (or another proxy/CDN) — do I need to configure anything? = Yes: enable "Trust proxy headers" under Advanced settings. Behind a proxy or CDN, every visitor reaches your server from the proxy's IP address — without this setting, one attacker's failed logins would count against everyone and could lock out all users. With it enabled, the plugin reads the visitor's real IP from `CF-Connecting-IP` (accepted only when the connection actually comes from Cloudflare's published IP ranges, so it cannot be spoofed), `X-Sucuri-ClientIP`, `Client-IP`, `X-Real-IP`, or `X-Forwarded-For`. The settings page shows a warning when it detects a proxy while this setting is off — and the reverse warning if it is on without a proxy in front, since that would allow IP spoofing. = Should I block XML-RPC? = If you don't use the WordPress mobile app or remote publishing tools like Windows Live Writer, blocking XML-RPC authentication removes a common attack vector. You can also choose to just apply delays without blocking it entirely. = Should I protect password reset requests? = Yes, for most sites. Attackers can abuse password reset forms to probe accounts or create noise during credential attacks. Password reset protection applies the same delay and lockout behavior used for login attempts, logs the source as `password-reset`, and keeps messages generic so the form does not reveal whether a username or email exists. = How do email notifications work? = When enabled, the plugin tracks failed login attempts per IP address. Once the threshold is reached (default: 5 attempts), an email is sent to alert you. The counter resets after one hour of no failed attempts from that IP. = Where can I see failed login attempts? = A dashboard widget shows the 10 most recent failed login attempts, including the time, username attempted, IP address, and source. It also includes a lightweight 7-day trends panel with daily totals, top sources, top IPs, and top targeted usernames. The widget is only visible to administrators (`manage_options`). Note: because the log records whatever was typed into the username field, a user who accidentally types their password there will have it shown in the widget and stored in the log — treat the log as sensitive. = How do I use fail2ban logging? = Enable fail2ban logging under `Settings` > `Login Delay Shield` > `Login Log`. If the log path is empty, Login Delay Shield writes to `login-delay-shield-fail2ban/login-delay-shield-fail2ban.log` in a plugin-owned temporary directory outside the WordPress uploads tree and adds basic `.htaccess`/`index.html` protections. Custom paths are restricted to the protected default directory by default; use the `wldelay_fail2ban_allowed_log_dirs` filter only for server-protected directories. If a custom path is rejected, logging is disabled instead of silently writing somewhere else. If lockout-event logging is enabled, an attempt that triggers a lockout may produce both a `failed login` line and a `lockout` line, so tune your jail's `maxretry` accordingly. The log is rotated to a single `.log.1` backup once it reaches 5 MB so it cannot grow without bound; adjust or disable this with the `wldelay_fail2ban_max_log_bytes` filter (return `0` to rely on system logrotate instead). Log lines include an ISO-8601 timestamp, stable prefix, and fields such as: `2026-05-04T12:00:00+00:00 Login Delay Shield: failed login source=wp-login ip=203.0.113.10 username=admin` A fail2ban filter can match the IP with a regex like: `failregex = Login Delay Shield: (?:failed login|lockout) .* ip=` = Is the admin interface accessible? = Yes! Login Delay Shield follows WCAG 2.1 accessibility guidelines. All settings are fully keyboard navigable, screen reader compatible, and include proper ARIA attributes. Collapsible sections can be toggled with Enter or Space keys, tooltips appear on focus (not just hover), and all dynamic changes are announced to assistive technologies. = Does this plugin work better with an object cache? = For high-traffic sites or sites experiencing frequent attacks, we recommend using a persistent object cache like Redis or Memcached. The plugin uses WordPress transients to track failed login attempts and lockouts per IP address. By default, transients are stored in the database. During a distributed brute-force attack (many IPs), this can create additional database queries. With an object cache installed: * Transient reads/writes go to memory instead of the database * Much faster performance under attack conditions * Reduced database load Popular object cache plugins: Redis Object Cache, W3 Total Cache, LiteSpeed Cache. Most managed WordPress hosts (WP Engine, Kinsta, Flywheel) include object caching by default. = What languages are supported? = Login Delay Shield is translated into 18 languages: * English (default) * Arabic (العربية) * Chinese Simplified (简体中文) * Czech (Čeština) * Dutch (Nederlands) * French (Français) * German (Deutsch) * Indonesian (Bahasa Indonesia) * Italian (Italiano) * Japanese (日本語) * Korean (한국어) * Polish (Polski) * Portuguese - Brazil (Português do Brasil) * Russian (Русский) * Spanish (Español) * Swedish (Svenska) * Thai (ไทย) * Turkish (Türkçe) * Vietnamese (Tiếng Việt) The plugin automatically uses your site's language setting. Want to help translate into another language? Visit [translate.wordpress.org](https://translate.wordpress.org/). == Screenshots == 1. Settings page with Security Setup Wizard and delay configuration options. 2. Email notification and IP lockout settings. 3. IP whitelist and XML-RPC protection settings. 4. Dashboard widget showing recent failed login attempts. == Contribute == Found a bug or want to suggest an improvement? Open a thread in the [support forum](https://wordpress.org/support/plugin/login-delay-shield/) on WordPress.org. Want to help translate the plugin into your language? Visit [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/login-delay-shield/). == Changelog == = 2.4.1 = Packaging fix. **Maintenance:** * The wordpress.org package no longer includes development files (test suite, Docker setup, build scripts, composer manifest). These were never loaded or executed by WordPress, but they inflated the download since 2.2.4. Added a `.distignore` so automated deploys ship only runtime files. * Fixed the packaging validation script omitting `uninstall.php`; the uninstall cleanup routine itself always shipped correctly. = 2.4.0 = Lockout-proof recovery and proxy/CDN awareness. **New Features:** * Proxy/CDN-aware IP detection — new supported headers behind the "Trust proxy headers" setting: `CF-Connecting-IP` (accepted only when the connection comes from Cloudflare's published IP ranges, so it cannot be spoofed), `X-Sucuri-ClientIP`, and `X-Real-IP`. * Proxy configuration health check on the settings page — warns when the site appears to be behind a proxy or CDN while header trust is disabled (mass-lockout risk), and when trust is enabled without a proxy in front (spoofing risk). * `WLDELAY_SAFE_MODE` emergency constant — define it as true in wp-config.php to disable all delays and lockouts while you recover access. A persistent admin warning shows while it is active. * Custom Login URL self-check — when the feature is enabled or the slug changes, the plugin verifies the new URL responds and automatically disables the feature if it would return a 404, instead of stranding everyone behind a dead login page. * The new custom login URL is emailed to the site admin as a recovery aid (disable with the `wldelay_send_custom_login_email` filter). **Security:** * All proxy header values are now validated as IP addresses; garbage values fall back to the connection IP instead of poisoning lockout keys. **Improvements:** * The Custom Login URL settings card now documents the `WLDELAY_DISABLE_CUSTOM_LOGIN` emergency bypass and prompts you to bookmark the new URL before enabling. * New FAQ entries: self-lockout recovery, Cloudflare/proxy configuration, custom login URL recovery, and ads/premium policy (there are none — and now the readme says so). = 2.3.4 = fail2ban logging hardening. **Improvements:** * The fail2ban log is now rotated to a single `.log.1` backup once it reaches 5 MB, preventing unbounded growth on installs without external log rotation. Configure or disable via the new `wldelay_fail2ban_max_log_bytes` filter. * Web-server protection files (`.htaccess`, `index.html`, `index.php`) are now written to every fail2ban log directory, including custom directories added via the `wldelay_fail2ban_allowed_log_dirs` filter. **Maintenance:** * Added an uninstall routine that removes plugin options, the failed-login log table, registered transients, and the plugin-owned fail2ban log directory when the plugin is deleted (multisite-aware). Custom fail2ban log paths are left untouched. = 2.3.3 = Security Setup Wizard. **New Features:** * Added a Security Setup Wizard with Conservative, Balanced, and Aggressive protection profiles. Applying a profile configures delay, progressive delay, lockout, email alerts, and authentication endpoint settings in one step while keeping every individual control editable. **Bug Fixes:** * Protection profiles no longer overwrite the log retention period, preventing unintended deletion of existing failed-login logs. * The current-profile badge now reflects the actual stored configuration and shows "Custom" after manual edits. * Pressing Enter in a settings field no longer applies a profile over manual edits; the Save Changes button now handles implicit form submission. **Security:** * The Aggressive profile counts lockouts by IP rather than IP + username, closing a password-spray gap where a single IP could try multiple usernames without locking out. = 2.3.2 = Password reset protection. **New Features:** * Added optional password reset protection that applies delay, lockout, whitelist bypass, and telemetry logging to password reset submissions. * Added password reset attempts as a distinct telemetry source (`password-reset`). * Added password reset protection to the settings page, feature summary, and Security Health Score. **Security:** * Password reset throttling uses isolated counters and lockouts so reset-form abuse cannot lock a user out of normal login. * Password reset attempts do not trigger failed-login email alerts. **Improvements:** * Admin recovery tools now clear password-reset-specific throttling keys. = 2.3.1 = Patch release with CI fixes and new telemetry feature. **New Features:** * Added "Top target pairs" card to telemetry — ranks the most common IP + username combinations from failed login attempts for faster incident triage. **Bug Fixes:** * Fixed unit tests failing in CI due to unmocked `get_option()` calls in fail2ban and sanitization test suites. * Fixed `wp plugin check` failing in CI because the plugin-check plugin was not activated before use. = 2.3.0 = Performance, UX, architecture, and CI improvements. **New Features:** * Security Health Score — 0-100 score card on the settings page showing protection posture with "next recommended" guidance. * "What's New" banner — dismissible notice after plugin upgrade highlighting new features. * Referral card — "Leave a review" and "Get support" links in the dashboard widget. * Contextual help links — tooltip system now supports optional "Learn more" URLs linking to documentation. * Pluggable username normalization — `wldelay_normalize_username` filter hook for LDAP, email-as-login, and SSO backends. * CI/CD test pipeline — GitHub Actions workflow running PHPUnit and wp plugin check on every push and PR. **Performance:** * Whitelist IP validation optimization — exact IPs checked via O(1) hash lookup; CIDR ranges iterated only on miss. Cached per-request. * Telemetry pagination drift detection — snapshot hash warns when data changes between pages during active attacks. = 2.2.4 = Top targeted usernames in login telemetry and hardening. **New Features:** * Added "Top usernames" card to the telemetry summary — ranks the most-targeted usernames by failed login attempts with description text for admin context. **Improvements:** * Added database index on the `username` column for faster GROUP BY queries at scale. * Username aggregation excludes NULL, empty, and whitespace-only values using parameterized TRIM filter. * Expanded PHPDoc return types for `wldelay_get_login_log_summary()` with full nested array structures. * Updated readme feature list and FAQ to mention top targeted usernames. = 2.2.3 = Complete Custom Login URL runtime, Trend Analytics queries, and bug fixes. **New Features:** * Custom Login URL runtime — custom slug now fully functional with login, logout, lost password, and password reset all routed through the custom URL. * Custom Login URL admin UI — settings card with enable/disable toggle, slug input, status badge, and tooltip help. * Trend Analytics query functions — `wldelay_get_top_ips()`, `wldelay_get_top_usernames()`, and `wldelay_get_daily_attempts()` for dashboard trend data. **Bug Fixes:** * Fixed double `wp_unslash()` on login username that could corrupt usernames with literal backslashes. * Fixed `wp_login_url` filter name (was `wp_login_url`, should be `login_url`) preventing URL rewriting. * Fixed canonical redirect leaking custom login slug via 302 when `/wp-login.php` is accessed through the front controller. * Fixed `login_init` blocking internal WordPress paths (e.g. `/wp/wp-login.php`) used for legitimate auth redirects. **Improvements:** * Expanded reserved slug list with `wp-json`, `wp-content`, `wp-includes`, `wp-signup`, `wp-activate`, `xmlrpc`, `feed`, `robots`, `sitemap`. * Replaced production `wldelay_unlock_current_ip_should_exit` filter with `WP_TESTS_DOMAIN` constant check — no longer exposes a testability surface in production. * Wrapped Custom Login URL section titles in `esc_html__()` for i18n completeness. * Added Custom Login URL to the protection features summary box. * Added Playwright end-to-end tests for full Custom Login URL verification. = 2.2.2 = Micro-hardening — input sanitization, i18n completeness, and code documentation. **Improvements:** * Added `wp_unslash()` before `sanitize_user()` in login username extraction to correctly handle WordPress magic-quote slashes. * Wrapped email notification subject and body in `__()` for full i18n/l10n support. * Added detailed inline comments explaining the IPv6 CIDR binary mask bit-shift logic. = 2.2.1 = Code housekeeping — JavaScript extraction and admin UI consistency. **Improvements:** * Extracted all inline JavaScript from the settings page view into a standalone `admin.js` file, loaded via `wp_enqueue_script()`. * Used `wp_localize_script()` to pass PHP-side translatable strings to JavaScript (Enabled/Disabled badge labels). * Standardized `