Description
Nahnu Login Shield does one thing and does it well: protect every WordPress login entry point. It is not a general security plugin. It is a login security plugin — hyperfocused on the login form, WooCommerce My Account, XML-RPC, the REST API, and all the form plugins your site might use.
No subscription. No license key. No upsell. Everything is included free.
Brute-force lockout
- Configurable attempt limit, time window, and short lockout duration.
- Escalating lockout — after N short lockouts the IP receives a long lockout (default 24 h).
- Per-username lockout — if the same username is tried from many different IPs (a distributed botnet), the username itself is locked across all IPs after a configurable threshold.
- Honeypot usernames — any attempt using a known-bad username (admin, root, administrator, test…) instantly locks the IP and the username globally. One attempt is enough.
- CIDR-supported IP blocklist and IP whitelist (IPv4 and IPv6).
- Manual unlock button next to any locked IP in the log.
Early blocking
An optional must-use plugin (mu-plugin) can fire before WordPress fully loads. It is off by default and never installed automatically: enable it from the General tab by clicking “Install early-block mu-plugin”, which copies nahnls-early-block.php into your must-use plugins directory (WPMU_PLUGIN_DIR). Once installed, locked-out IPs are turned away at the door — no PHP framework, no database writes, no WooCommerce — for a fraction of the normal request cost. Honeypot IPs receive a full static block page for all site pages during the lockout period. Clicking “Remove” (or deactivating/deleting the plugin) deletes the file again.
CAPTCHA / Verification providers
Choose one provider site-wide. The widget appears on every protected form automatically.
- OOPSpam — checkbox widget powered by OOPSpam Anti-Spam. Adds login-specific overrides: block VPN/proxy, datacenter IPs, and disposable email addresses.
- reCAPTCHA v2 — Google checkbox challenge.
- reCAPTCHA v3 — Google invisible score-based. No user interaction required.
- hCaptcha — privacy-first, free tier.
- Cloudflare Turnstile (managed) — Cloudflare decides whether to show a challenge based on risk signals.
- Cloudflare Turnstile (invisible) — entirely silent.
- Friendly Captcha — EU-hosted, proof-of-work, no cookies. GDPR-ready.
- None — disable the widget entirely. Geo-blocking, lockout, and honeypot still apply.
Two-factor authentication
- When 2FA is on, every user in the roles you select (all roles by default) is asked for a one-time code after their password, on every login.
- Email codes are always on as the baseline – nobody can skip the second step. Users can add an authenticator app (TOTP) or a passkey for stronger protection; the code is emailed automatically only when they have nothing stronger.
- Users cannot turn 2FA off for themselves; administrators can reset another user’s methods (for example after a lost phone), which falls back to email codes.
- Backup codes as a universal fallback.
- Emergency recovery:
define( 'NAHNLS_DISABLE_2FA', true );in wp-config.php switches the 2FA check off. - Challenge happens on wp-login.php, or right inside WooCommerce My Account for customers.
- Per-user setup on a dedicated Login & security page (wp-admin) or the My Account tab – codes are confirmed inline, with a scannable QR code and manual key.
Passwordless sign-in
- Passkeys (WebAuthn) – sign in with Touch ID, Windows Hello, a phone or a security key. Browser autofill (conditional mediation) supported. Built-in verifier (ES256, RS256, Ed25519), no external service.
- Magic links – one-time email login links; hashed at rest, rate-limited, go through lockout, geo and CAPTCHA checks. Per-user opt-out.
- Both available on wp-login.php and the WooCommerce My Account form; role allow-lists.
White-label emails
- Email branding (Settings tab) – the 2FA code, magic link and signup confirmation emails use your sender name and address, logo, accent and header colours and footer text. Subject, heading and intro of each email are editable with placeholders; preview in the browser and send yourself a test email. No plugin branding in emails unless you switch on the optional credit line.
Access control
- Restrict roles to IP addresses / CIDR ranges (IPv4 + IPv6).
- Login and logout redirects per role.
- XML-RPC kill switch (403 before WordPress loads).
- Signup email verification with automatic pruning of unconfirmed accounts.
- Security checklist with a score and one-click fixes.
Geo-fencing
Reads the visitor country from CDN headers with zero latency and zero API quota. Supported headers: CF-IPCountry (Cloudflare), CDN-Country (Bunny.net), CloudFront-Viewer-Country (AWS CloudFront), X-Vercel-IP-Country (Vercel), X-Country-Code (Fastly/Akamai), X-Geo-Country (generic CDN).
Falls back to freeipapi.com or ipinfo.io (both free) with a configurable transient cache when no CDN header is present.
Edge WAF integration
- Bunny.net WAF — push locked-out IPs to Bunny WAF with one click. Blocks them at the CDN edge before they reach your server.
- Cloudflare WAF (IP Access Rules) — same for Cloudflare. Uses the IP Access Rules API (not the 5-rule WAF limit). Auto-expiry via WP-Cron.
- Both integrations support triggering on honeypot hits, on brute-force lockout, or both.
- Edge block log in the admin with IP, provider, reason, age, expiry, and one-click unblock.
JavaScript presence check
A hidden field filled by a small inline script gates every submission. Headless bots that skip JavaScript execution are blocked before CAPTCHA or lockout checks run — saving API quota.
Custom login URL
Optionally rename /wp-login.php to a secret path. Requests to the old URL return a configurable response: 404, redirect to home, or redirect to a custom URL.
REST API protection
- Block unauthenticated access to /wp-json/wp/v2/users (username enumeration).
- Apply lockout and IP block rules to REST API authentication.
- Optionally disable Application Passwords.
Session security
- Idle session timeout with configurable duration and a 2-minute warning dialog.
- Force logout all sessions immediately with one button (useful during an incident).
Login notifications
- Notify each user by email on account login with IP and device info.
- Alert admin emails whenever any administrator logs in.
- New-country alert: email the first time a user logs in from a previously unseen country.
- Weekly security digest: failed attempts, lockouts, honeypot hits, top attacking IPs, top targeted usernames.
WooCommerce My Account
Extend all protections – geo-blocking, lockout, honeypot, CAPTCHA, JS check, passkeys, magic links – to the WooCommerce My Account login, registration, lost-password, checkout, and order-tracking forms. Each form has its own toggle. Customers get a Login & security tab in My Account to manage passkeys, two-factor authentication and sign-in options without ever seeing wp-admin, and complete the 2FA challenge on the My Account page itself. The same panel is available anywhere via the [nahnls_account_security] shortcode.
Form plugin integrations
Three form integrations are supported, each with its own toggle on the Integrations tab: Bricks Builder forms (enabled per form from the Bricks form element’s Spam Protection panel), SureForms (all forms, or only the forms you pick), and the native WordPress comment form. Each adds the JS marker check and the configured CAPTCHA widget to the form and validates it server-side on submit. Any other form, theme template or headless front end can integrate through the developer API: template tags, [nahnls_passkey_button] / [nahnls_magic_link_form] shortcodes, 20+ actions and filters, REST routes under nahnls/v1 and the window.NAHNLSAuth JavaScript API (see docs/API.md).
Full event log
- Search by IP, username, or detail text.
- Filter by event type (fail, lockout, honeypot, geo block, success, captcha fail…).
- Date range picker.
- CSV export.
- Log detail column shows the source form or endpoint for every event (WooCommerce My Account, REST API, XML-RPC, Bricks Builder Form, WP Login form, etc.).
- Configurable retention period and row cap.
Analytics
- Daily activity chart (30-day view).
- Attack time-of-day heatmap.
- Event breakdown donut chart.
- Top attacking IPs table.
- Top targeted usernames table.
- All data is local — no external analytics service.
MainWP extension
A companion extension (Nahnu Login Shield — MainWP Extension) is available separately and allows site managers to view login shield data — lockouts, edge WAF blocks, recent events, settings summary — across all connected child sites from the MainWP dashboard, with a widget on each site overview page and a per-site detail tab.
Privacy
Nahnu Login Shield stores IP addresses and usernames in a local WordPress database table for the event log. The retention period and row cap are configurable. No data is sent to any Nahnu server. Third-party CAPTCHA providers and IP lookup services receive only the data required for their function, per their own privacy policies. Deleting the plugin removes all stored data via the included uninstall.php.
External services
Nahnu Login Shield works fully offline by default. Each service below is contacted only when the site administrator explicitly selects or configures it in the plugin settings. No data is ever sent to a Nahnu server.
- Google reCAPTCHA (v2 / v3) — Used as the CAPTCHA provider when selected on the Widget tab. The visitor’s browser loads the widget script from
https://www.google.com/recaptcha/api.json protected forms, and Google receives the visitor’s IP address and browser information per its own policy. On every form submit the plugin sends the reCAPTCHA response token together with your reCAPTCHA secret key server-to-server tohttps://www.google.com/recaptcha/api/siteverify. Terms of Service · Privacy Policy - hCaptcha — Used as the CAPTCHA provider when selected. The visitor’s browser loads the widget from
https://js.hcaptcha.com/1/api.json protected forms (hCaptcha receives the visitor’s IP and browser data). On form submit the plugin sends the hCaptcha response token and your secret key server-to-server tohttps://api.hcaptcha.com/siteverify. Terms of Service · Privacy Policy - Cloudflare Turnstile — Used as the CAPTCHA provider when selected (managed or invisible mode). The visitor’s browser loads the widget from
https://challenges.cloudflare.com/turnstile/v0/api.json protected forms (Cloudflare receives the visitor’s IP and browser data). On form submit the plugin sends the Turnstile token and your secret key server-to-server tohttps://challenges.cloudflare.com/turnstile/v0/siteverify. Terms of Service · Privacy Policy - Friendly Captcha — Used as the CAPTCHA provider when selected. The widget script itself is bundled with the plugin (no CDN), but the visitor’s browser fetches a proof-of-work puzzle from
api.friendlycaptcha.comwhile the form is displayed (Friendly Captcha receives the visitor’s IP address). On form submit the plugin sends the solved puzzle token (frc-captcha-solution) plus your site key and your Friendly Captcha API key server-to-server tohttps://global.frcapi.com/api/v2/captcha/siteverify. Terms of Service · Privacy Policy - OOPSpam Anti-Spam — Used as the verification provider when selected; requires the separate OOPSpam Anti-Spam plugin with its own API key. On login/registration submit (and optionally at widget render) the plugin calls the OOPSpam plugin, which sends the visitor’s IP address and the submitted username/email, together with your OOPSpam API key, to the OOPSpam API to obtain a spam/VPN/data-centre score. Terms of Service · Privacy Policy
- Bunny.net API (
https://api.bunny.net) — Used only when the Bunny WAF edge-blocking integration is enabled on the Edge Blocking tab. When a lockout or honeypot event fires, the plugin sends the blocked IP address, an expiry timestamp and a short note, authenticated with your Bunny API key, to the Bunny WAF IP-block endpoint for your pull zone. The “Test connection” button reads your pull zone details with the same key. Terms of Service · Privacy Policy - Cloudflare API (
https://api.cloudflare.com/client/v4) — Used only when the Cloudflare WAF edge-blocking integration is enabled. When a lockout or honeypot event fires, the plugin creates an IP access rule containing the blocked IP address and a note, authenticated with your Cloudflare API token; a scheduled task later deletes that rule when the block expires, and the “Test connection” button verifies the token. Terms of Service · Privacy Policy - freeipapi.com — Geo-lookup fallback, used only when geo-blocking is enabled and “freeipapi” is selected as the API source and no CDN country header is present. On a login attempt the plugin sends the visitor’s IP address (and your optional API key as a bearer token) to
https://free.freeipapi.com/api/json/{ip}to learn its country code; results are cached locally per IP. Terms of Use · Privacy Policy - ipinfo.io — Geo-lookup fallback, used only when geo-blocking is enabled and “ipinfo” is selected as the API source. On a login attempt the plugin sends the visitor’s IP address (and your optional API token) to
https://api.ipinfo.io/lite/{ip}to learn its country code; results are cached locally per IP. Terms of Service · Privacy Policy - Self-hosted Cap and mCaptcha instances — If you select the Cap or mCaptcha provider you must enter the URL of an instance you host. The visitor’s browser loads that instance’s widget script, and on form submit the plugin sends the challenge token and your secret key server-to-server to
{your-instance}/siteverify(Cap) or{your-instance}/api/v1/pow/siteverify(mCaptcha). No third party is involved unless you point the URL at one; see the Cap and mCaptcha documentation for their respective terms. - ALTCHA — If selected, verification is handled locally by the separately installed ALTCHA plugin; this plugin makes no external request for it.
Country flags shown in the admin log are SVG files bundled with the plugin (assets/img/flags/); no flag CDN or other external image service is used.
Bundled third-party libraries
No scripts are loaded from a CDN. These libraries ship inside the plugin (assets/vendor/ unless noted otherwise):
- Chart.js 4.4.1 — MIT License, https://www.chartjs.org. Used on the Analytics admin tab.
- Friendly Captcha widget (friendly-challenge 0.9.18) — MIT License, https://github.com/FriendlyCaptcha/friendly-challenge. Used only when Friendly Captcha is the selected CAPTCHA provider.
- flag-icons (country flag SVGs in assets/img/flags/) — MIT License, https://github.com/lipis/flag-icons. Displayed in the admin event log; loaded from the plugin folder, never from a CDN.
Screenshots









Installation
- Upload the
nahnu-login-shieldfolder to/wp-content/plugins/. - Activate through Plugins in the WordPress admin.
- Go to Settings > Login Shield to configure.
Quick start:
- On the Widget tab, choose a CAPTCHA provider (or leave it as None to use lockout only).
- On the Lockout tab, review the attempt thresholds.
- On the Geo tab, enable geo-blocking and select blocked countries if needed.
- On the Integrations tab, enable protections for WooCommerce My Account, Bricks Builder forms, SureForms and WordPress comments as needed.
- On the Edge Blocking tab, connect your Bunny.net or Cloudflare account to push blocks to the edge.
To use OOPSpam, install and activate the OOPSpam Anti-Spam plugin and enter your API key there first. Nahnu Login Shield detects it automatically.
FAQ
-
Do I need OOPSpam installed?
-
No. OOPSpam is one of seven CAPTCHA provider options. All other providers and all other features work independently.
-
Does this plugin slow down my site?
-
No. Assets load only on wp-login.php and any pages you opt into via the Integrations tab. The JS check uses a tiny inline script with no external request. Geo lookups from CDN headers are zero-latency. API fallback results are cached per IP for a configurable period. If you opt in to the early-block mu-plugin, it turns away locked-out IPs before WordPress fully loads.
-
What happens if the CAPTCHA API goes down?
-
All providers fail open. A network or API error never locks out legitimate users.
-
Will this conflict with other security plugins?
-
It hooks specifically into WordPress login actions and standard WooCommerce and form-plugin hooks. If another plugin hooks the same actions it may conflict. Open a support thread with details if you see an issue.
-
Can I use this without any CAPTCHA?
-
Yes. Set the provider to None. Geo-blocking, brute-force lockout, honeypot username trapping, the JS presence check, and 2FA all work independently of CAPTCHA.
-
Does it work with WooCommerce?
-
Yes. The WooCommerce My Account login, registration, lost-password, checkout, and order-tracking forms each have their own toggle in the WooCommerce section of the Integrations tab.
-
I locked myself out. How do I get back in?
-
Two options. First, add
define( 'NAHNLS_BYPASS_IP', 'your.ip.address' );to wp-config.php (above the “That’s all, stop editing” line). It accepts a comma-separated list of IPs and CIDR ranges, and exempts them from lockouts, the IP blocklist, geo blocking and the early-block rate limit – no deactivation needed. Second, rename the plugin folder fromnahnu-login-shieldto_nahnu-login-shieldvia SFTP, which deactivates it. If you are stuck at the two-factor step instead, see the next question. -
Can I put my own username on the honeypot list?
-
Yes, and it is a good idea: usernames are public (author pages, feeds, the REST API), so bots try them. Add your username to the Honeypot list on the Lockout tab and sign in with your email address from then on. Anyone who tries the username is locked out like a bot. Email addresses of real accounts are never blocked, so patterns like
admin@*do not affect your own sign-in. The Lockout tab lists which entries are real accounts and reminds you if your own username is on the list. If you get locked out anyway, adddefine( 'NAHNLS_DISABLE_HONEYPOT', true );to wp-config.php (above the “That’s all, stop editing” line) to switch the honeypot list off, sign in, fix the list, then remove the line. To clear an IP that is already locked, also see the question above. -
I can’t receive the 2FA email / I’m locked out
-
When two-factor authentication is on, every sign-in asks for a code, and email codes are the baseline. If the email does not arrive (a mail problem on the server, spam filtering), first try a backup code or “Email me a code” again. If that does not help, add
define( 'NAHNLS_DISABLE_2FA', true );to wp-config.php (above the “That’s all, stop editing” line). This switches the 2FA check off for everyone, so you can sign in with just your password; the plugin’s settings page shows a notice while it is set. Fix email delivery (an SMTP plugin usually does it), add an authenticator app on your Login & security page so you are not dependent on email, then remove the line. Note:NAHNLS_BYPASS_IPdoes NOT bypass 2FA – it only exempts addresses from lockouts, the IP blocklist, geo blocking and the early-block rate limit. -
Can my login, registration and My Account pages be cached?
-
Mostly yes. Every protected form carries a hidden security token that lives about a day. On page load the plugin’s small script fetches a fresh token (one request to admin-ajax.php), so pages served from a full-page cache keep working as long as JavaScript runs – including cached blog posts with a comment form. Without JavaScript, a cached copy of the form older than a day fails the security check and asks the visitor to reload. Login, registration and My Account pages should still be excluded from full-page caching (WooCommerce requires this anyway). Developers can lengthen the token life with the
nahnls_form_nonce_lifefilter. -
Is this compatible with multisite?
-
It is not tested on multisite and is not recommended for network activation.
-
How does geo-blocking work when I do not use a CDN?
-
It falls back to one of two free IP geolocation APIs (freeipapi.com or ipinfo.io) with a configurable cache. CDN headers are always checked first and take priority.
-
How do I get a new form plugin supported?
-
Open a support thread or submit a request at https://nahnuloginshield.com/support/. Bricks Builder, SureForms and WordPress comments are the supported integrations today; anything else can be wired up through the developer API (docs/API.md) while a request is considered.
-
What is the mu-plugin and is it safe to have?
-
The mu-plugin is optional and opt-in. Nothing is copied until you, as an administrator, click Install early-block mu-plugin on the General tab. That button copies
nahnls-early-block.phpfrom the plugin folder into your must-use plugins directory (WPMU_PLUGIN_DIR, normallywp-content/mu-plugins/). The file checks whether the requesting IP is locked out or rate-limited and, if so, sends a 403/429 response before WordPress fully loads — dramatically reducing server load during bot floods. The General tab shows whether it is installed, not installed or outdated; after a plugin update you are offered an Update mu-plugin button (it is never refreshed automatically). Clicking Remove deletes the file, and it is also removed on plugin deactivation and deletion. If the directory is not writable, the settings page shows manual copy instructions instead.The per-IP rate-limit counters the mu-plugin uses are tiny text files stored inside your WordPress uploads directory at
wp-content/uploads/nahnu-login-shield/ratelimit/(protected by anindex.phpand.htaccess; files older than five minutes are pruned automatically). Nothing is written outside the WordPress installation; the folder is deleted when the plugin is uninstalled. -
Can attackers bypass the lockout by rotating IPs?
-
The per-username lockout catches this: if the same username is attempted from too many different IPs within the time window, the username itself is locked regardless of IP. Adding common targets (admin, administrator, root) to the Honeypot username list gives the strongest protection — one attempt from any IP is enough to lock both the IP and the username globally.
Reviews
There are no reviews for this plugin.
Contributors & Developers
“Nahnu Login Shield” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “Nahnu Login Shield” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Changelog
1.0.11
- Changed: real usernames on the honeypot list are blocked again. Usernames are public, so you can put your own on the list and sign in with your email address instead; bots that try the username are locked out. Email addresses of real accounts are never treated as honeypots. The Lockout tab shows which entries are real accounts and warns you when your own username is on the list.
- New:
define( 'NAHNLS_DISABLE_HONEYPOT', true );in wp-config.php switches the honeypot username list off (emergency recovery, see FAQ). - Fixed: SureForms. The widget was added by a browser-side scan that looked for a submit button SureForms 2.x no longer marks as type=”submit”, so nothing was ever injected and the check could not pass. The JS marker and CAPTCHA are now printed by the server inside each SureForms form (above the submit button, or after the fields for inline buttons).
- New: protect all SureForms forms or only the ones you tick (Integrations tab, shown while the SureForms integration is on).
- Fixed: with reCAPTCHA v3 or invisible Turnstile, a SureForms form could not submit; inside SureForms forms the token is now kept fresh in the form at all times.
- Fixed: the hidden form nonce was tied to the logged-in user, so a logged-in administrator testing a form that submits over the REST API (SureForms) always got “Security check expired”. The nonce now depends on the site and time only.
1.0.10
- New: white-label emails. Settings > Email branding sets the sender name and address, a logo from the media library, accent and header colours, footer text and the subject / heading / intro (with placeholders) of the 2FA code, magic link and signup confirmation emails, which now share one template. Includes a browser preview and a “Send test email” button. The “Nahnu Shield” badge and “Sent by Nahnu Login Shield” footer are gone; an opt-in “Protected by Nahnu Login Shield” line is available (off by default). New filters nahnls_email_args and nahnls_email_html.
- Changed: two-factor authentication now protects everyone it applies to. With 2FA on, every user in the roles selected under “Apply to roles” (all roles by default; roles added later are covered too) is asked for a code on every password login. Email codes are always on as the baseline; users can add an authenticator app or passkey for stronger protection, and the code is emailed automatically only to users with nothing stronger. Users can no longer turn 2FA off for themselves; administrators can “Reset 2FA” for another user (falls back to email codes). The Remind / Enforce mode is gone: sites that used Enforce keep their selected roles, every other site now applies 2FA to all roles. New wp-config.php recovery switch NAHNLS_DISABLE_2FA (see FAQ).
- Security: every form the plugin protects (wp-login.php, WooCommerce, comments, SureForms, Bricks, nahnls_widget_html()) now carries a hidden form nonce that is verified before any protection field is read; the 2FA challenge form, the emailed magic-link and signup-verification links and the 2FA challenge URLs carry their own nonces, verified before the token or key is read. Developers rendering their own CAPTCHA markup must echo nahnls_form_nonce_field() (see docs/API.md). The nonce is refreshed by JavaScript on page load, so cached pages keep working (see FAQ).
- Fixed: a honeypot entry that is also a real account (the default list includes admin, support, info@* and admin@*) locked that account’s owner out for 24 hours as soon as they signed in with it. Real usernames and emails are no longer treated as honeypots; the Lockout tab shows which entries are ignored for that reason, and “Add to honeypot” in the log refuses real accounts.
- Fixed: with REST protection on, every anonymous REST request that carried an Authorization header WordPress does not understand (Bearer / JWT tokens from other plugins, server-level HTTP auth) counted as a failed login, so five ordinary API calls could lock out an IP. Only credentials WordPress actually rejected count now.
- Fixed: on servers with APCu, the failed-attempt counter was not reset by a successful login.
- Changed: the signup confirmation email’s subject, heading and message are edited on the Access tab, directly below the signup verification settings, with their own Preview and “Send test” buttons. Sender, logo and colours stay under Settings > Email branding, which now holds the two-factor and magic-link wording.
- Fixed: on the Edge Blocking tab, the status badge could say “Not configured” right after saving a key that was saved fine (it reflected a momentary API check instead of the saved settings) and then “Active” after a reload. The badge now shows the saved state, with a new “Key saved, choose a zone” step; the API check result is shown under the key field.
- Improved: the “Active IP lockouts” table has a “Triggered by” column (form or API, number of failed attempts, username, time) and marks your own IP.
- Changed: the username lockout and honeypot-username checks on wp-login.php now run from the wp_authenticate action with the username WordPress hands over, instead of reading the posted form on login_init.
- Changed: plugin prefix renamed from the three-letter “n”+”ls” to “nahnls” to meet the WordPress.org prefix guideline. Functions, classes, options, the log table, user meta, cron hooks, shortcodes, hooks, the REST namespace (now nahnls/v1) and JavaScript globals (window.NAHNLSAuth etc.) all use the new prefix. A one-time migration runs automatically on upgrade and carries over settings, the event log, user 2FA/passkey data and cron schedules. The wp-config constant is now NAHNLS_BYPASS_IP (the old name is still honoured). Developers integrating via docs/API.md must update their hook, shortcode, REST and JS names.
- Removed: the server-side handlers for ~40 form, membership, LMS and community plugins that were present in the code but not exposed in the UI. The supported integrations are Bricks Builder, SureForms and WordPress comments; everything else integrates through the developer API.
- Changed: the early-block mu-plugin is now strictly opt-in. It is no longer copied into the must-use plugins directory on activation, update or settings-page load; install, update or remove it with the explicit buttons on the General tab. Existing installs: the old copy is removed during migration, so click “Install early-block mu-plugin” if you want it back.
- Changed: the mu-plugin’s per-IP rate-limit counters now live in wp-content/uploads/nahnu-login-shield/ratelimit/ (with index.php and .htaccess) instead of the system temp directory, and are removed on uninstall.
- Changed: every script and style is a static file loaded with wp_enqueue_script / wp_enqueue_style; dynamic values are passed with wp_localize_script. The plugin prints no inline in the injector; the JS-presence marker is now set on injected forms and the OOPSpam widget initialises on them.
- Fixed: Unchecked toggles in the Forms tab could inherit their previously saved value.
- Performance: removed per-request opcache invalidation (it was disabling opcache for the whole plugin); now runs once on activation/update.
- Performance: WAF pushes (Bunny/Cloudflare) and notification emails on lockouts and honeypot hits now run asynchronously via WP-Cron instead of inside the attacker’s request.
- Performance: widget CSS/JS and CAPTCHA provider scripts are only enqueued on pages that actually render a protected form.
- Performance: added (event, created_at) index to the log table; schema version 1.1.
- Performance: DB-version check and mu-plugin install no longer run on every front-end request.
- Performance: analytics queries and Chart.js only load on the Analytics tab and are cached for 5 minutes; username-lockout scan only on the Lockout tab.
- Performance: log table geo/lockout lookups batched into a single query; fixed a geo cache key that never matched.
- Performance: idle-timeout activity write throttled to once per minute.
- Fixed: dashboard widget and CSV export were silently capped at 50 rows; MainWP stats queried a non-existent table.
1.0.3
- Fixed: WooCommerce checkout captcha showing when “Protect WC checkout” toggle is off.
- Fixed: WC checkout/order-tracking only active when another WC option was also enabled.
- Fixed: OOPSpam widget CSS/JS not enqueued on the WC checkout page (assets were only loaded on My Account pages).
1.0.2
- Security hardening: json_validate() guards on all external API responses.
- Fixed void function return type error in nahnls_ajax_bunny_fetch_zones().
- Fixed parse errors caused by PHP string escaping in settings and form-protector classes.
- Fixed multi-value match arm syntax for broader PHP 8.x compatibility.
1.0.1
- Minor stability and compatibility fixes.
1.0.0
- Initial public release.
- Brute-force lockout with escalation, per-username lockout, and honeypot usernames.
- CAPTCHA providers: OOPSpam, reCAPTCHA v2/v3, hCaptcha, Cloudflare Turnstile (managed/invisible), Friendly Captcha.
- Two-factor authentication (TOTP) with per-role enforcement.
- Geo-blocking via CDN headers (Cloudflare, Bunny, CloudFront, Vercel, Fastly) with IP API fallback.
- Edge WAF integration for Bunny.net and Cloudflare with auto-expiry and block log.
- JavaScript presence check.
- Custom login URL with configurable 404/redirect behaviour.
- REST API protection: username enumeration block, lockout enforcement, Application Password toggle.
- Session security: idle timeout with warning dialog, force-logout all sessions.
- Login notifications: per-login email, admin alerts, new-country alerts, weekly digest.
- WooCommerce My Account integration: login, registration, lost-password, checkout, order tracking.
- Form protection framework with CAPTCHA + JS marker for form plugins and WooCommerce.
- Full event log with search, filter, date range, CSV export, and source-form detail column.
- Analytics: daily chart, time-of-day heatmap, event breakdown, top IPs, top usernames.
- Early-block mu-plugin: locked-out IPs blocked before WordPress loads.
- APCu support for attempt counters (100x faster than DB transients on supported hosts).
- MainWP extension companion plugin available separately.
