Developer Reference
Hooks, public functions, database schema, transient keys, and internals for Login Delay Shield v2.6.0.
Architecture
Login Delay Shield is a single-plugin WordPress security plugin. Its main files:
| File | Purpose |
|---|---|
wp-login-delay.php | Main plugin file — all security logic, hooks, database management, dashboard widget |
wldelay-settings.php | Settings registration via WordPress Settings API, sanitization, validation |
wldelay-settings-view.php | LDS_Settings_View class — admin UI rendering (collapsible sections, status badges, summary box, telemetry, audit log) |
wldelay-features.php | LDS_Features — single source of truth for every option key, type, and default |
wldelay-pipeline.php | wldelay_process_failed_attempt() — the single failure-processing pipeline shared by wp-login, XML-RPC, REST, application-password, and password-reset |
wldelay-botnet.php | Distributed-attack (botnet) detection — per-username distinct-IP tracking, alert fan-out, recent-detection feed |
wldelay-recovery.php | Emergency Recovery URL — opt-in secret token that lets a locked-out admin clear their own IP lockout without admin or server access |
wldelay-persistence.php | Durable lockout store (WLDelay_DB_Persistence) and the lockout database table |
wldelay-audit.php | Admin/security audit log — table, write path, health/recovery tracking |
wldelay-async.php | Event firehose and deferred-task queue (shutdown/cron flush) |
wldelay-fail2ban.php | fail2ban log writing, path validation, and rotation |
wldelay-enumeration.php | Username/author enumeration hardening |
wldelay-privacy.php | GDPR personal-data exporter and eraser registration |
wldelay-migration.php | Settings-version migration runner |
wldelay-changelog.php | In-admin changelog page parsed from readme.txt |
admin.js | Admin JavaScript — collapsible sections, tooltips, and UI interactions |
admin.css | Admin styles for the settings page and dashboard widget |
uninstall.php | Uninstall routine — removes options, the log table, registered transients, and the plugin-owned fail2ban log directory on plugin deletion |
Failed-attempt counters live in WordPress transients. Active lockouts are written to a dedicated database table through a filterable persistence store (WLDelay_DB_Persistence), with a transient layer for fast lookups and a graceful fallback to transient-only state when the durable write fails. Three custom tables back the failed login log, the lockout store, and the admin/security audit log. A lightweight deferred-task queue moves non-critical writes (audit rows, expired-lockout purges) off the request hot path to shutdown or a daily cron backstop. Every authentication surface (wp-login, XML-RPC, REST, application passwords, password reset) routes failures through one pipeline, wldelay_process_failed_attempt(), which emits a failed_attempt event; distributed-attack detection subscribes to that event and defers its cross-IP query to the same queue. Custom Login URL uses WordPress rewrite rules. No custom REST API endpoints or post types are registered.
When the plugin is deleted, uninstall.php removes its options, the failed-login log table, registered transients, and the plugin-owned fail2ban log directory. The cleanup is multisite-aware and leaves any custom fail2ban log path untouched. Since v2.3.4.
Constants
| Constant | Value | Description |
|---|---|---|
WLDELAY_VERSION | '2.6.0' | Plugin version |
WLDELAY_PLUGIN_FILE | __FILE__ | Main plugin file path |
WLDELAY_OPTION_NAME | 'wldelay_options' | WordPress option name for all settings |
WLDELAY_DB_VERSION | '7' | Schema version, tracked in the wldelay_db_version option to drive dbDelta() upgrades |
Emergency Constants
Define these in wp-config.php to recover access. They are read with defined() — the plugin never sets them.
| Constant | Effect |
|---|---|
WLDELAY_SAFE_MODE | When true, disables every delay, lockout, and tracking path (login, XML-RPC, REST, application passwords, password reset) — the plugin behaves as if every IP were whitelisted. A persistent, non-dismissible admin notice shows while it is active. Since v2.4.0. |
WLDELAY_DISABLE_CUSTOM_LOGIN | When defined, bypasses the Custom Login URL feature so /wp-login.php works again. Since v2.4.0. |
Emergency Recovery Constants
Tuning constants for the Emergency Recovery URL. Each is defined with a defined() guard, so you can override it in wp-config.php. Since v2.6.0.
| Constant | Value | Description |
|---|---|---|
WLDELAY_RECOVERY_QUERY_VAR | 'wldelay_recovery' | Query var carrying the recovery token: /?wldelay_recovery=<token> |
WLDELAY_RECOVERY_NAG_DAYS | 90 | Token age (days) after which the settings page nags you to rotate it |
WLDELAY_RECOVERY_RL_MAX | 5 | Failed token attempts allowed per IP inside the rate-limit window |
WLDELAY_RECOVERY_RL_WINDOW | 900 | Rate-limit window in seconds (15 minutes) |
WLDELAY_RECOVERY_REVEAL_TTL | 300 | Seconds the freshly generated URL stays available to view/download (5 minutes) |
Filter Hooks
wp_authenticate_user
filter · priority 1Main authentication filter. Applies delays and lockouts to login attempts. Parameters: $user (WP_User|WP_Error), $password. Returns WP_Error if locked out, otherwise applies sleep() delay on failure and returns $user.
rest_authentication_errors
filter · priority 20REST API authentication protection. Applies delays and lockout to failed REST API authentication when wldelay_rest_enabled is on. Returns a 403 WP_Error if the IP is locked out. Respects the IP whitelist and skips application-password attempts (handled separately). Since v2.1.2.
authenticate
filter · priority 25Application-password authentication protection. Applies delays and lockout to failed Basic Auth attempts when wldelay_application_password_enabled is on. Detects app-password attempts via PHP_AUTH_USER/PHP_AUTH_PW server headers. Parameters: $user, $username, $password. Since v2.1.2.
authenticate
filter · priority 99Blocks XML-RPC authentication when the “block XML-RPC” setting is enabled. Parameters: $user, $username, $password. Returns WP_Error('wldelay_xmlrpc_blocked') for XML-RPC requests.
wldelay_2fa_providers
filterCustomize the list of recognized 2FA plugins for the settings-page health check. Receives an associative array of slug => [ plugin_basename, … ]. Return the modified array to add or remove providers. The default map includes two-factor, wp-2fa, mini-orange, and google-authenticator. Since v2.1.4.
wldelay_2fa_coverage_checkers
filterRegister provider-specific callbacks that report privileged-user 2FA coverage for the settings-page notice. Receives a map of provider_slug => callable (the built-in entry covers the two-factor plugin). Each callback returns an array with keys supported, privileged_total, protected, unprotected, and unknown.
wldelay_normalize_username
filterCustomize how usernames are normalized for failure tracking and lockout identity. Receives the normalized username and the original input. Return your own normalized value to support LDAP, email-as-login, or SSO backends where the default lowercase/sanitize behavior is not enough. Since v2.3.0.
wldelay_persistence_store
filterSwap the lockout persistence backend. Receives null; return a WLDelay_Persistence implementation to replace the default DB-backed store (for example a Redis or external store) without touching call sites. Falls back to WLDelay_DB_Persistence when the filter returns anything else.
wldelay_login_help_url
filterSet the “Need help getting in?” link shown in the login-page lockout feedback block. Defaults to the site’s lost-password URL (respecting any custom-login-slug rewrite); point it at a support or docs page instead. Returned value is escaped at output.
wldelay_fail2ban_allowed_log_dirs
filterExtend the set of directories a custom fail2ban log path may live in. By default only the plugin-owned protected directory is allowed; add server-protected directories here. Web-server protection files (.htaccess, index.html, index.php) are written to every allowed directory. A custom path outside the allowed set disables logging rather than writing elsewhere.
wldelay_fail2ban_max_log_bytes
filterControl the size at which the fail2ban log is rotated to a single .log.1 backup. Defaults to 5 MB. Return 0 to disable in-plugin rotation and rely on system logrotate instead. Since v2.3.4.
wldelay_botnet_ip_threshold
filterNumber of distinct source IPs targeting one username that triggers a distributed-attack alert. The stored option is clamped to [2, 100] before the filter runs, and the result is re-floored at 2 so a filter cannot drop below the minimum. Default 5. Since v2.5.0.
wldelay_botnet_window
filterSliding detection window, in seconds. The stored minutes option is clamped to [5, 60] and converted to seconds before the filter; the result is floored at MINUTE_IN_SECONDS. Default 900 (15 minutes). Since v2.5.0.
wldelay_botnet_alert_cooldown
filterPer-username cooldown, in seconds, that suppresses repeat distributed-attack alerts after one fires. Default HOUR_IN_SECONDS. Since v2.5.0.
wldelay_cloudflare_ip_ranges
filterThe list of Cloudflare CIDR ranges used to validate the CF-Connecting-IP header. The header is honored only when REMOTE_ADDR falls inside one of these ranges, so it cannot be spoofed. Return your own array to extend or replace the built-in IPv4/IPv6 ranges. Since v2.4.0.
wldelay_send_custom_login_email
filterWhether to email the new login URL to the site admin when the Custom Login URL feature is enabled or its slug changes. Default true; return false (e.g. __return_false) to suppress the recovery email. Since v2.4.0.
wldelay_help_base_url
filterBase URL for the “Learn more” documentation links on each settings card. Defaults to https://damoiseau.xyz/docs/login-delay-shield/user-guide/. Return an empty string to hide the links, or point them at your own mirror. Since v2.5.0.
wldelay_export_login_log_should_exit
filterControls whether the CSV export handler calls exit() after streaming. Default true. Set to false in test environments. Since v2.1.2.
plugin_action_links_{basename}
filterAdds a “Settings” link to the plugin’s entry on the Plugins page.
Action Hooks
wp_login_failed
actionHooked to log failed login attempts to the database. Parameters: $username. Records the IP address, username, timestamp, and source (wp-login, xmlrpc, rest, application-password, or password-reset).
wldelay_cleanup_logs
action · customCustom cron hook that runs daily. Deletes log entries older than the configured retention period. Processes in batches of 1,000 rows.
wp_dashboard_setup
actionRegisters the “Recent Failed Login Attempts” dashboard widget.
admin_post_wldelay_unlock_current_ip
actionHandles the “Unlock Current IP” button on the settings page. Removes the lockout and failure counters for the requesting admin’s IP address. Nonce-protected and requires manage_options capability.
admin_post_wldelay_export_login_log
actionHandles CSV export of the failed login log. Streams results in 1,000-row batches with formula-injection protection. Reads optional filters from $_GET (source, IP, username, date range). Nonce-protected and requires manage_options capability. Since v2.1.2.
admin_post_wldelay_recovery_generate
actionMints a new Emergency Recovery token, stores its hash, emails the URL to the site admin, and sets the one-time reveal. Nonce-protected, requires manage_options. Since v2.6.0.
admin_post_wldelay_recovery_download
actionStreams the once-revealed recovery URL as a .txt download. Nonce-protected, requires manage_options; only works inside the reveal window. Since v2.6.0.
admin_post_wldelay_recovery_confirm / admin_post_nopriv_wldelay_recovery_confirm
actionHandles the confirm POST from the recovery landing page. Re-validates the token and nonce, then clears the caller’s own IP lockout (it never logs the user in). Registered for both authenticated and unauthenticated callers so a locked-out admin can use it. Since v2.6.0.
wldelay_event_{event}
action · customPer-event hook fired for each plugin event (e.g. wldelay_event_cron_tick). Receives the event payload array. Subscribe with wldelay_on_event(), or hook the generic wldelay_event action (which receives $event_name, $payload) to observe every event as an audit/SIEM firehose.
wldelay_persistence_write_failed
action · customFires when a lockout could not be written to the durable store. Parameters: $ip, $type (login or password-reset). The lockout falls back to transient-only state until the store recovers.
wldelay_audit_write_failed
action · customFires when an audit-log row could not be written. Parameters: $action (the action key whose row was lost), $error (DB error string).
Error Codes
| Error Code | Context | Description |
|---|---|---|
wldelay_ip_locked | Login form | IP or IP+username is locked out. Message includes a countdown. |
wldelay_xmlrpc_blocked | XML-RPC | XML-RPC authentication is disabled on this site. |
wldelay_attempts_remaining | Login form | Shows remaining attempts before lockout (appended to existing error). |
Core Functions
| Function | Returns | Description |
|---|---|---|
wldelay_get_options() | array | Get cached plugin options. Uses a global cache to avoid repeated get_option() calls. |
wldelay_clear_options_cache() | void | Clear the in-memory options cache. Called automatically when settings are updated. |
wldelay_get_delay_value( $failure_count ) | int | Calculate the delay in seconds based on current settings (fixed, random, or progressive) and the number of failures. |
wldelay_get_client_ip() | string | Get the client IP address. Proxy headers are read only when wldelay_trust_proxy_headers is enabled: CF-Connecting-IP (honored only when REMOTE_ADDR is a Cloudflare edge), X-Sucuri-ClientIP, Client-IP, X-Real-IP, then X-Forwarded-For. Each candidate is validated as an IP; a garbage value falls through to REMOTE_ADDR. |
wldelay_get_cloudflare_ip_ranges() | array | Built-in Cloudflare IPv4/IPv6 CIDR ranges, filterable via wldelay_cloudflare_ip_ranges. Since v2.4.0. |
wldelay_is_cloudflare_remote_addr( $remote_addr ) | bool | Whether the TCP peer is within a Cloudflare range (gates CF-Connecting-IP trust). Since v2.4.0. |
wldelay_detect_proxy_headers() | array | Human-readable names of any proxy/CDN forwarding headers present on the current request. Since v2.4.0. |
wldelay_get_proxy_health_status() | array | Returns status (misconfigured-cdn, spoofable, ok, or none) and the detected headers, powering the settings-page proxy health notice. Since v2.4.0. |
wldelay_process_failed_attempt( $username, $source, $context ) | void | Unified failure-processing pipeline shared by all authentication surfaces. Logs the attempt, updates counters/lockout, and emits the failed_attempt event. Since v2.5.0. |
wldelay_get_lockout_attempt_strategy( $options ) | string | Returns 'ip' or 'ip_username'. |
wldelay_normalize_username( $username ) | string | Lowercases and sanitizes a username for consistent tracking. |
wldelay_get_attempt_identifier( $ip, $username, $options ) | string | Returns $ip or $ip|$username based on strategy. |
wldelay_get_requested_login_username() | string | Gets the normalized username from $_POST['log']. |
wldelay_is_xmlrpc_request() | bool | Checks XMLRPC_REQUEST constant and REQUEST_URI. |
wldelay_is_rest_request() | bool | Checks if the current request is a WordPress REST API request. Since v2.1.2. |
wldelay_is_application_password_attempt() | bool | Checks for PHP_AUTH_USER and PHP_AUTH_PW server headers (Basic Auth). Since v2.1.2. |
wldelay_get_login_source() | string | Returns 'xmlrpc', 'rest', 'application-password', 'password-reset', or 'wp-login'. |
wldelay_get_login_source_label( $source ) | string | Human-readable label for a source key, used by the dashboard widget and log views. |
wldelay_custom_login_is_active() | bool | Whether the Custom Login URL feature is enabled and the slug is valid. Since v2.2.3. |
wldelay_get_custom_login_slug() | string | Returns the configured custom login slug (default 'my-login'). Since v2.2.3. |
Lockout Functions
| Function | Returns | Description |
|---|---|---|
wldelay_is_ip_locked( $ip, $username ) | bool | Check if an IP (or IP+username) is currently locked out. |
wldelay_lock_ip( $ip, $username ) | void | Lock an IP/username. Sets a transient with the lockout duration. |
wldelay_get_failure_count( $ip, $username ) | int | Get the current failure count for an IP/username. |
wldelay_get_lockout_remaining_seconds( $ip, $username ) | int | Seconds remaining on a lockout (0 if not locked). |
wldelay_get_lockout_duration_seconds( $options ) | int | Get the configured lockout duration in seconds. |
wldelay_get_lockout_error_message( $ip, $username ) | string | Build the lockout error message with human-readable countdown. |
wldelay_track_failed_attempt( $username ) | int | Increment failure counter, trigger lockout and/or email if thresholds are met. |
wldelay_send_notification_email( $ip, $username, $attempts ) | void | Send email alert (respects site-wide cooldown). |
Whitelist Functions
| Function | Returns | Description |
|---|---|---|
wldelay_is_ip_whitelisted( $ip ) | bool | Check if an IP is in the whitelist. Returns false if whitelist is disabled. |
wldelay_ip_in_range( $ip, $range ) | bool | Check if an IP matches a single address or CIDR range. Supports both IPv4 and IPv6. |
Recovery Functions
| Function | Returns | Description |
|---|---|---|
wldelay_delete_lockout_for_ip( $ip ) | int | Remove all lockout and failure transients for a specific IP. Returns the count of removed entries. Cleans up both ip and ip_username strategy keys. |
wldelay_flush_lockout_transients() | int | Clear all lockout and failure transients site-wide. Uses the transient registry and falls back to a database scan. Returns total entries removed. |
wldelay_register_transient_key( $key ) | void | Add a transient key to the registry (wldelay_transient_registry option). Called automatically when failure/lockout transients are created. |
wldelay_unregister_transient_key( $key ) | void | Remove a transient key from the registry. Called automatically during cleanup. |
Botnet Detection Functions
Distributed-attack detection (wldelay-botnet.php) watches distinct source IPs per targeted username inside a sliding window and alerts — it never blocks. The COUNT query is deferred to the shutdown task queue off the request hot path. Since v2.5.0.
| Function | Returns | Description |
|---|---|---|
wldelay_botnet_is_enabled() | bool | Whether distributed-attack detection is on (default on). |
wldelay_botnet_get_ip_threshold() | int | Distinct-IP threshold that triggers an alert. Clamped to [2, 100]; default 5. |
wldelay_botnet_get_window_seconds() | int | Detection window in seconds. Option clamped to [5, 60] minutes; default 900. |
wldelay_botnet_get_recent_detections() | array | Rolling list of recent detections (newest first, up to 20, 24h TTL) that feeds the dashboard banner. |
On detection the plugin writes a synchronous botnet_detected audit-log row, pushes an entry onto the dashboard feed, and — when email notifications are enabled — sends an alert email naming the targeted username and up to five sample IPs.
fail2ban Config & Coherence
| Function | Returns | Description |
|---|---|---|
wldelay_fail2ban_generate_filter_config() | string | Builds the fail2ban filter.d config (with the current failregex) for download. Since v2.5.0. |
wldelay_fail2ban_generate_jail_config() | string | Builds the matching jail.local stanza for the active log path. Since v2.5.0. |
wldelay_handle_fail2ban_config_download() | void | admin-post handler that streams the generated filter/jail config as a download. Nonce-protected, requires manage_options. Since v2.5.0. |
wldelay_buffer_fail2ban_line( $event, $ip, $username, $source ) | void | Buffers a fail2ban line for the request instead of writing immediately. Since v2.5.0. |
wldelay_flush_fail2ban_buffer() | void | Writes the buffered fail2ban lines once per request (one disk write instead of one per attempt). Since v2.5.0. |
wldelay_settings_coherence_warnings( $options ) | array | Returns non-blocking warnings for contradictory settings (empty whitelist, unwritable fail2ban path, alert thresholds that can never fire), shown on the settings page. Since v2.5.0. |
Emergency Recovery URL
An opt-in, time-boxed secret URL (/?wldelay_recovery=<token>) that lets a locked-out admin clear their own IP lockout with no admin or server access. Only the token’s sha256 hash is stored; the raw token is shown once. A GET renders a confirm page and the unlock fires from a nonce-protected POST, so email and antivirus link-prefetchers cannot trigger it. It never logs the user in and never disables the shield. Since v2.6.0.
| Function | Returns | Description |
|---|---|---|
wldelay_recovery_is_enabled() | bool | Whether the Emergency Recovery URL feature is switched on (default off). |
wldelay_recovery_generate_token() | string | Mint a 256-bit token, store only its sha256 hash plus the generation timestamp, and return the raw token (the only time it exists in plaintext). |
wldelay_recovery_hash( $token ) | string | sha256 hex of a raw token. |
wldelay_recovery_token_matches( $token ) | bool | Constant-time (hash_equals) comparison of a candidate token against the stored hash. |
wldelay_recovery_build_url( $token ) | string | Build the full recovery URL for a raw token. |
wldelay_recovery_generated_age_days() | int|null | Age of the current token in whole days, or null when never generated. |
wldelay_recovery_needs_rotation() | bool | Whether the token is older than WLDELAY_RECOVERY_NAG_DAYS (90) and should be rotated. |
wldelay_recovery_rate_limit_hit( $ip ) | bool | Record a failed attempt for an IP and report whether it is now over the limit (WLDELAY_RECOVERY_RL_MAX per WLDELAY_RECOVERY_RL_WINDOW). Only failed attempts count. |
wldelay_recovery_send_email( $url ) | void | Email the recovery URL to the site admin address. |
The feature audits every event through the audit log: recovery_url_generated, recovery_used, recovery_failed, and recovery_rate_limited. Handlers run on the admin_post_* hooks listed under Action Hooks.
Database Functions
| Function | Returns | Description |
|---|---|---|
wldelay_get_log_table_name() | string | Returns the full table name: {$wpdb->prefix}wldelay_login_log. |
wldelay_create_log_table() | void | Creates or upgrades the log table using dbDelta(). Called on activation and version upgrades. |
wldelay_log_failed_attempt( $ip, $username, $source ) | void | Inserts a row into the log table and invalidates the dashboard cache. |
wldelay_get_recent_failed_attempts( $limit ) | array | Returns recent failed attempts ordered by time (used by the dashboard widget). |
wldelay_cleanup_old_logs() | void | Deletes entries older than the retention period in batches of 1,000. |
Export & Log Query Functions
Added in v2.1.2–2.2.3 for CSV export, filtered log queries, and trend analytics.
| Function | Returns | Description |
|---|---|---|
wldelay_get_login_log_attempts( $limit, $offset, $filters ) | array | Query log entries with optional filters (source, IP, username, date range) and pagination. All filters are applied at the SQL level. |
wldelay_get_export_login_log_url( $filters ) | string | Build a nonce-protected URL for the CSV export endpoint. Accepts optional filter array. |
wldelay_handle_export_login_log() | void | Main CSV export handler. Streams results in 1,000-row batches with flush() after each batch. Sets Content-Type: text/csv and Content-Disposition: attachment headers. |
wldelay_sanitize_login_log_filters( $filters ) | array | Validate and sanitize filter input. Checks IP format, date format (Y-m-d), and auto-swaps reversed date ranges. |
wldelay_get_login_log_filters_from_request() | array | Extract filters from $_GET using expected keys only (wldelay_log_source, wldelay_log_ip, wldelay_log_username, wldelay_log_from, wldelay_log_to). |
wldelay_csv_sanitize_cell( $value ) | string | Mitigate CSV formula injection by prepending a single quote to values starting with =, +, -, @, or whitespace. |
wldelay_get_top_ips( $days, $limit ) | array | Returns the top attacking IP addresses over the last $days days (default 7), limited to $limit results (default 10). Each row has ip_address and attempt_count. Since v2.2.3. |
wldelay_get_top_usernames( $days, $limit ) | array | Returns the most targeted usernames over the last $days days (default 7), limited to $limit results (default 10). Each row has username and attempt_count. Since v2.2.3. |
wldelay_get_daily_attempts( $days ) | array | Returns daily failed attempt totals for the last $days days (default 7). Each row has day (Y-m-d) and attempt_count. Since v2.2.3. |
Database Schema
{$wpdb->prefix}wldelay_login_log
| Column | Type | Key | Description |
|---|---|---|---|
id | bigint(20) unsigned | PRIMARY | Auto-increment ID |
ip_address | varchar(45) | INDEX | IP address (supports IPv4 and IPv6) |
username | varchar(60) | INDEX | Username attempted |
attempted_at | datetime | INDEX | Timestamp of the attempt |
source | varchar(20) | INDEX | wp-login, xmlrpc, rest, application-password, or password-reset |
username_attempted | — | COMPOSITE INDEX | Composite (username, attempted_at) index — added in schema gen 7 to count distinct IPs per username within a time window for distributed-attack detection |
Created on plugin activation via dbDelta(). Schema version is tracked in the wldelay_db_version option.
{$wpdb->prefix}wldelay_lockouts
Durable lockout store behind WLDelay_DB_Persistence. Lockouts survive object-cache eviction and transient expiry; the transient_key column links a row to its companion transient.
| Column | Type | Key | Description |
|---|---|---|---|
id | bigint(20) unsigned | PRIMARY | Auto-increment ID |
lockout_key | varchar(64) | UNIQUE | Hashed attempt identifier |
ip_address | varchar(45) | INDEX | IP address (IPv4 or IPv6) |
username | varchar(255) | — | Username, for the ip_username strategy |
lockout_type | varchar(20) | — | login or password-reset |
source | varchar(20) | — | Originating auth surface |
transient_key | varchar(191) | — | Companion transient name |
generation | varchar(32) | — | Generation token for bulk-clear matching |
created_at | datetime | — | When the lockout was set |
expires_at | datetime | INDEX | When the lockout expires |
{$wpdb->prefix}wldelay_audit_log
Admin/security audit trail. Rows record settings changes, lockout clears, and whitelist edits. Writes are deferred to shutdown through the task queue so they stay off the request hot path.
| Column | Type | Key | Description |
|---|---|---|---|
id | bigint(20) unsigned | PRIMARY | Auto-increment ID |
actor_id | bigint(20) unsigned | INDEX | WP user ID, or 0 for system |
actor_login | varchar(60) | — | Actor login name |
action | varchar(50) | INDEX | Action key, e.g. settings_changed |
object | varchar(191) | — | The thing acted on |
old_value | text | — | Previous value (JSON for arrays) |
new_value | text | — | New value (JSON for arrays) |
ip_address | varchar(45) | — | Actor IP address |
created_at | datetime | INDEX | When the action occurred |
Transient Keys
All transient keys use MD5 hashing of the attempt identifier for security (prevents enumeration).
| Key Pattern | TTL | Value | Description |
|---|---|---|---|
wldelay_fails_{md5(id)} | 1 hour | int (count) | Failed attempt counter per IP or IP+username |
wldelay_lockout_{md5(id)} | Lockout duration | int (timestamp) | Lockout start time per IP or IP+username |
wldelay_email_cooldown | Cooldown minutes | int (timestamp) | Site-wide email rate limiter |
wldelay_dash_recent | 1 minute | array | Cached recent failed attempts for the dashboard widget |
wldelay_dash_trends | 5 minutes | array | Cached 7-day trend snapshot for the dashboard widget |
wldelay_botnet_detections | 24 hours | array | Rolling feed of recent distributed-attack detections (newest first, max 20) for the dashboard banner (v2.5.0+) |
wldelay_botnet_cd_{md5(username)} | 1 hour | int (timestamp) | Per-username alert cooldown that suppresses repeat distributed-attack alerts (v2.5.0+) |
wldelay_recovery_rl_{md5(ip)} | 15 minutes | int (count) | Failed recovery-token attempts per IP, for the rate limiter (v2.6.0+) |
wldelay_recovery_reveal_{user_id} | 5 minutes | string (URL) | One-time reveal of a freshly generated recovery URL for the settings page / .txt download (v2.6.0+) |
The id in failure and lockout keys is the attempt identifier: either the IP address (ip strategy) or IP|username (ip_username strategy).
Options Reference
All settings are stored in a single serialized array under the wldelay_options WordPress option.
| Key | Type | Default | Description |
|---|---|---|---|
wldelay_delay | int | 1 | Fixed delay in seconds (0–10) |
wldelay_delay_random | bool | false | Use random delay |
wldelay_delay_random_min | int | 1 | Min random delay (1–10) |
wldelay_delay_random_max | int | 5 | Max random delay (1–10) |
wldelay_progressive_enabled | bool | false | Enable progressive delay |
wldelay_progressive_increment | int | 1 | Increment per attempt (1–10) |
wldelay_progressive_max | int | 30 | Max progressive delay (5–60) |
wldelay_email_enabled | bool | false | Enable email notifications |
wldelay_email_threshold | int | 5 | Failures before email (1–100) |
wldelay_email_address | string | '' | Notification email (fallback: admin_email) |
wldelay_email_cooldown | int | 5 | Minutes between emails (0–60) |
wldelay_lockout_enabled | bool | false | Enable IP lockout |
wldelay_lockout_threshold | int | 10 | Failures before lockout (1–100) |
wldelay_lockout_duration | int | 60 | Lockout duration in minutes (1–1440) |
wldelay_lockout_attempt_strategy | string | 'ip' | 'ip' or 'ip_username' |
wldelay_trust_proxy_headers | bool | false | Trust X-Forwarded-For headers |
wldelay_whitelist_enabled | bool | false | Enable IP whitelist |
wldelay_whitelist_ips | string | '' | Newline-separated IPs/CIDR ranges |
wldelay_log_retention_days | int | 30 | Log retention in days (0–365, 0 = forever) |
wldelay_xmlrpc_enabled | bool | false | Enable XML-RPC protection |
wldelay_xmlrpc_block | bool | false | Block all XML-RPC auth |
wldelay_rest_enabled | bool | false | Enable REST API protection (opt-in, injected default) (v2.1.2+) |
wldelay_application_password_enabled | bool | false | Enable application-password protection (opt-in, injected default) (v2.1.2+) |
wldelay_password_reset_enabled | bool | false | Enable password-reset protection (opt-in) (v2.3.2+) |
wldelay_enumeration_hardening_enabled | bool | false | Enable username/author enumeration hardening (opt-in) |
wldelay_fail2ban_enabled | bool | false | Enable fail2ban log writing (opt-in) (v2.3.4+) |
wldelay_fail2ban_log_path | string | '' | Custom fail2ban log path; empty uses the plugin-owned protected directory (v2.3.4+) |
wldelay_fail2ban_include_lockouts | bool | true | Also write lockout lines, not just failed-login lines (v2.3.4+) |
wldelay_botnet_enabled | bool | true | Distributed-attack detection — alert-only, never blocks (injected default) (v2.5.0+) |
wldelay_botnet_ip_threshold | int | 5 | Distinct IPs per username before an alert (clamped 2–100) (v2.5.0+) |
wldelay_botnet_window_minutes | int | 15 | Detection window in minutes (clamped 5–60) (v2.5.0+) |
wldelay_protection_profile | string | '' | Active Security Setup Wizard profile (conservative, balanced, aggressive, or empty/custom) (v2.3.3+) |
wldelay_custom_login_enabled | bool | false | Enable custom login URL (v2.2.3+) |
wldelay_custom_login_slug | string | 'my-login' | Custom login URL slug (lowercase, numbers, hyphens only) (v2.2.3+) |
wldelay_recovery_enabled | bool | false | Enable the Emergency Recovery URL (opt-in) (v2.6.0+) |
wldelay_recovery_token_hash | string | '' | sha256 hash of the current recovery token (never the raw token) (v2.6.0+) |
wldelay_recovery_generated_at | string | '' | UTC timestamp the token was generated (drives the rotation nag) (v2.6.0+) |
wldelay_recovery_last_used_at | string | '' | UTC timestamp the recovery URL was last used (v2.6.0+) |
Other WordPress Options
| Option Name | Description |
|---|---|
wldelay_db_version | Database schema version (tracks WLDELAY_VERSION) |
wldelay_plugin_version | Current plugin version |
wldelay_previous_version | Previous version (for upgrade detection) |
wldelay_name_change_notice_dismissed | Whether the rename notice was dismissed |
wldelay_whats_new_dismissed | Version whose “What’s New” notice was dismissed |
wldelay_settings_version | Settings-schema version, drives the migration runner |
wldelay_fail2ban_default_token | Random token for the plugin-owned fail2ban log directory name |
wldelay_transient_registry | Array of active transient keys (used by flush-lockouts for cleanup) |
Cron Jobs
| Hook | Schedule | Function | Description |
|---|---|---|---|
wldelay_cleanup_logs | Daily | wldelay_cleanup_old_logs() | Deletes log entries older than the retention period. Processes in batches of 1,000 to avoid table locks. Scheduled on wp hook, unscheduled on deactivation. |
wldelay_async_cron | Daily | wldelay_run_async_cron() | Emits the cron_tick event and flushes the deferred-task queue, giving expired-lockout purges and other queued work a backstop on low-traffic sites. Scheduled on wp hook, cleared on deactivation. |
WP-CLI
Login Delay Shield registers commands under the login-delay-shield namespace. Available when WP-CLI is installed.
| Command | Arguments | Description |
|---|---|---|
wp login-delay-shield unlock-ip <ip> | <ip> — IPv4 or IPv6 address | Removes lockout and failure transients for the specified IP. Validates IP format. Returns count of removed entries. |
wp login-delay-shield flush-lockouts | — | Clears all lockout and failure transients site-wide. Uses the transient registry and falls back to a database scan. Also clears the registry itself. |