Description
Nahnu Login Shield protects every WordPress login entry point: the login form, WooCommerce My Account, XML-RPC, the REST API and your form plugins. It is a login security plugin, not a general security suite.
No subscription. No license key. No upsell. Everything is free.
Brute-force lockout
- Attempt limit, time window and lockout duration, with escalating lockouts (default 24 h) for repeat offenders.
- Per-username lockout stops distributed botnets trying one username from many IPs.
- Honeypot usernames (admin, root, test…) lock the IP on the first attempt.
- IP blocklist and allowlist with CIDR ranges (IPv4 and IPv6), and one-click unlock.
Early blocking
An optional must-use plugin turns locked-out IPs away before WordPress loads. It is off by default and never installed automatically: click “Install early-block mu-plugin” on the General tab, and “Remove” to delete it again.
CAPTCHA / verification providers
One provider site-wide, shown on every protected form: OOPSpam (with VPN, data-centre and disposable-email blocking), reCAPTCHA v2 and v3, hCaptcha, Cloudflare Turnstile (managed or invisible), Friendly Captcha, Cap, mCaptcha and ALTCHA, or none.
Two-factor authentication
- Every user in the roles you choose gets a one-time code after their password. Email codes are always on; users can add an authenticator app (TOTP) or a passkey.
- Backup codes, admin reset for lost phones, and an emergency switch:
define( 'NAHNLS_DISABLE_2FA', true );. - Works on wp-login.php and inside WooCommerce My Account.
Passwordless sign-in
- Passkeys (WebAuthn): Touch ID, Windows Hello, phones and security keys, with browser autofill. Built-in verifier, no external service.
- Magic links: one-time email login links, hashed at rest and rate-limited, with a per-user opt-out.
White-label emails
2FA codes, magic links and signup confirmations use your sender name, logo, colours and footer. Subject, heading and intro are editable, with a preview and test send.
Access control
- Restrict roles to IP addresses or CIDR ranges.
- Login and logout redirects per role.
- XML-RPC kill switch.
- Signup email verification with pruning of unconfirmed accounts.
- Security checklist with a score and one-click fixes.
Geo-fencing
Reads the visitor country from CDN headers (Cloudflare, Bunny.net, CloudFront, Vercel, Fastly/Akamai) with no API quota, falling back to freeipapi.com or ipinfo.io with caching.
Edge blocking: Bunny.net and Cloudflare
- Push locked-out and honeypot IPs to the Bunny.net or Cloudflare WAF so attackers are stopped at the CDN edge, with automatic expiry and a block log with one-click unblock.
- Bunny Shield: one-click login rate limit (covers wp-login.php, xmlrpc.php and your custom login path), blocked IPs on a Shield access list, “Block network” for attacker ASNs, and warnings for learning mode or log-only WAF. Works with Nahnu Cache Connector for Bunny.net.
- Cloudflare WAF: one-click login rate limit on every plan (fixed limits on Free, your own on Pro and up), added next to your existing rules; blocked IPs with Unblock; “Block network” on every plan.
More protection
- JavaScript presence check blocks headless bots before CAPTCHA or lockout checks run.
- Custom login URL renames /wp-login.php; the old URL returns 404 or redirects.
- REST API: block user enumeration, apply lockouts to REST authentication, optionally disable Application Passwords.
- Sessions: idle timeout with a warning, and force-logout of all sessions.
- Notifications: login emails, admin-login alerts, new-country alerts and a weekly security digest.
WooCommerce My Account
All protections extend to the My Account login, registration, lost-password, checkout and order-tracking forms, each with its own toggle. Customers get a Login & security tab to manage passkeys, 2FA and sign-in options without seeing wp-admin. The same panel is available anywhere with the [nahnls_account_security] shortcode.
Form plugin integrations
Bricks Builder forms (per form), SureForms (all forms or the ones you pick) and the WordPress comment form, each with its own toggle. Anything else can integrate through the developer API: template tags, shortcodes, actions and filters, REST routes under nahnls/v1 and a JavaScript API (see docs/API.md).
Event log and analytics
- Search, filter by event type and date, and export to CSV. Every event shows the form or endpoint it came from.
- Daily activity, time-of-day heatmap, event breakdown, and top attacking IPs and targeted usernames.
- Configurable retention and row cap. All data stays on your site.
MainWP extension
A separate companion extension shows lockouts, edge blocks, recent events and settings across all your MainWP child sites.
Privacy
IP addresses and usernames are stored in a local database table for the event log, with configurable retention. No data is sent to any Nahnu server. Third-party services you enable receive only what they need (see below). Deleting the plugin removes all stored data.
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 - Bunny Shield API (
https://api.bunny.net/shield/...) — Used only when a Bunny connection is configured on the Edge Blocking tab (your own key, or the one from the Nahnu Cache Connector for Bunny.net plugin when you choose to reuse it). The Edge Blocking tab reads your Shield zone’s plan and status, and when you click “Turn off learning mode” or “Switch the WAF to Block” it changes that one setting on your Shield zone. When you click “Create rule”, the plugin creates (and later updates or removes) one rate-limit rule containing your login paths and the limits you chose. When a lockout is pushed to Bunny and the pull zone has Shield, it creates a Shield access list with the first IP and sends it the IP addresses of locked-out visitors, removing them when the lockout expires or you unlock them. When you block a network, it sends the AS number to a second access list. All requests are authenticated with your Bunny API key. Terms of Service · Privacy Policy - Team Cymru IP-to-ASN mapping (DNS:
origin.asn.cymru.com,origin6.asn.cymru.com,asn.cymru.com) — Used only when an administrator clicks “Block network…” on a locked-out IP. The plugin makes a DNS lookup containing that IP address (reversed, as DNS requires) to find the network (AS number and name) it belongs to, and shows it for confirmation before anything is blocked. No other data is sent. Service description · 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 (or when you unlock the IP), and the “Test connection” button verifies the token. On the Cloudflare WAF tab, the plugin reads the zone’s name and plan, and, only when you use them, creates, updates or deletes one rate limiting rule (the login paths and the limits) and IP access rules for the network AS numbers you block. 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.15
- Changed: the account security panel in right-to-left languages (Arabic, Hebrew, Persian): the sign-in arrow icon is mirrored, and email addresses stay readable inside right-to-left text.
- Fixed: on some themes and with translation plugins, the account security panel’s card icons sat above the title instead of beside it. The icon and its text now always share one row.
- Changed: the plugin description on WordPress.org is shorter (under the 2,500-word limit, so it is no longer cut off), with the same features listed more compactly.
1.0.14
- Changed: the front-end account security panel (WooCommerce My Account > Security and the [nahnls_account_security] shortcode) has a cleaner, store-style design: each section is a card with an icon and a one-line summary, and every sign-in method and two-factor method is a row with its status (On, Always on, Not set up) or a switch. Email login links are turned on and off with a switch. The two-factor section no longer repeats the email address four times: one status line, one row per method, backup codes as a row, and a short tip when email codes are the only method. Works with phone screens and right-to-left languages, and follows the existing colour variables and dark palette.
1.0.13
- New: Cloudflare WAF sub-tab on Edge Blocking, with the same tools as Bunny Shield.
- New: Cloudflare login rate limit. An on/off switch adds a rate limiting rule to your zone (through the Cloudflare API) that blocks any IP flooding wp-login.php, xmlrpc.php or your custom login path. It is added next to any rate limiting rules you already have, never replacing them. On the Free plan the limits are Cloudflare’s fixed ones (more than 10 requests in 10 seconds blocked for 10 seconds); on Pro and up you choose the requests, the time window and the block length (up to 1 hour on Pro, 1 day on Business). The rule follows your custom login path, including subdirectory installs. Needs the API token permission Zone Zone WAF Edit; the tab says so if it is missing.
- New: blocked IPs on Cloudflare are listed on the Cloudflare WAF tab with an Unblock button, and unlocking an IP on the Lockout tab now lifts its Cloudflare block right away instead of waiting for it to expire.
- New: “Block network…” blocks attacker networks (AS numbers) on Cloudflare too, on every Cloudflare plan (IP Access Rules). With Bunny Shield Advanced and Cloudflare both connected, one click blocks the network on both.
- New: the Cloudflare WAF tab warns when the zone is paused in Cloudflare (nothing is blocked then).
- Fixed: Cloudflare blocks were never lifted when they expired: the scheduled unblock task failed. Blocks now end on time.
- Fixed: blocking an IP that was already blocked on Cloudflare lost the record of its rule, so it could not be unblocked from the plugin.
- Fixed: status shown on the settings page now follows a save without a reload: the custom login path On/Off badge, the paths the Bunny rate limit covers and the cache note, “API key is set” notes and placeholders, the 2FA status box, the header status and the blocked-IP log (unblocking on the Bunny Shield tab also removes the row on the IP Blocking tab).
- Fixed: the Cloudflare “Test connection” button appears right after saving the token and zone, without a reload.
- Changed: the custom login path cache note on the Bunny Shield tab links to Nahnu Cache Connector for Bunny.net on WordPress.org.
1.0.12
- New: Bunny Shield login rate limit. An on/off switch on the Edge Blocking tab creates a Shield rate-limit rule (directly through the Bunny API, no other plugin needed) that blocks any IP flooding wp-login.php, xmlrpc.php or your custom login path. On the free Shield Basic plan the limits are fixed at what Bunny allows there (more than 10 requests in 10 seconds blocked for 1 minute); on Shield Advanced and up you choose the requests, time window and block length (up to 1 hour). Floods are stopped at the edge before they reach your server. When you change the custom login path, the rule is updated automatically.
- Fixed: after saving the Bunny API key or pull zone, the key line could show “✗ Request failed” and the Bunny Shield tab still said “Connect Bunny.net first” until a reload. Both now update right away, and the Test connection button appears without a reload.
- New: blocked IPs on the edge. When your pull zone has Bunny Shield, lockouts go to a Shield access list (“Nahnu Login Shield lockouts”, action Block) automatically: it propagates globally within seconds, the Bunny Shield tab lists every blocked IP with an Unblock button, entries are removed when the lockout expires or you unlock the IP, and the pull-zone IP block list stays as the automatic fallback. The list is created with the first blocked IP (Bunny does not accept an empty list) and removed when it empties. Bunny error messages are now shown in full instead of a bare HTTP code.
- New: “Block network…” on active lockouts looks up the IP’s network (AS number) and, after you confirm, blocks the whole network on a Shield access list. Attackers rotate IPs but usually stay on the same hosting network. Blocked networks are listed (and can be unblocked) on the Edge Blocking tab. Needs Shield Advanced: the free plan allows one custom access list, which is used for blocked IPs.
- New: the Bunny Shield tab shows your Shield plan and warns when Shield is in learning mode or the WAF is set to “Log only”, the two settings that silently stop Shield from blocking. Each warning has a one-click fix (“Turn off learning mode”, “Switch the WAF to Block”).
- New: works with the Nahnu Cache Connector for Bunny.net. Reuse its Bunny connection instead of entering the API key twice, and the custom login path is kept out of its edge HTML cache automatically (updated as soon as you change the path). Without the Cache Connector, the Bunny Shield tab reminds you to exclude the custom login path from Bunny’s HTML cache yourself.
- Changed: the Edge Blocking tab has two sub-tabs: IP Blocking (the Bunny.net and Cloudflare IP blocks and the blocked-IP log, as before) and Bunny Shield (rate limit, lockout access list and blocked networks). The tab you used last is remembered.
- Changed: the “Access temporarily restricted” and geo-block pages are now sent with HTTP 403 and no-cache headers instead of 200, so no CDN or page cache can store a block page and serve it to other visitors.
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.
