WP Manifestindependent plugin directory
manifest / security / google-security-for-wordpress

Google Security for WordPress

A Google-powered security suite for WordPress: reCAPTCHA v3 scoring on the WordPress and WooCommerce login, registration, lost password, and checkout forms, plus two-factor authentication (TOTP) compatible with Google Authenticator. Works with or without WooCommerce.

by One Dog Solutions · github.com/onedogsolutions/google-security-for-wordpress

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/onedogsolutions/google-security-for-wordpress/archive/refs/heads/main.zip

A Google-powered security suite for WordPress: reCAPTCHA v3 scoring on the login, registration, lost password, and WooCommerce checkout forms, plus two-factor authentication (TOTP) compatible with Google Authenticator. Works with or without WooCommerce.

Description

Google Security for WordPress bundles two complementary layers of protection for your site's accounts:

  1. Invisible bot scoring with Google reCAPTCHA v3 on the WordPress login, registration, and lost password screens — and, when WooCommerce is active, the customer login, registration, and checkout flows — to stop carding, credential brute-forcing, and spam accounts.
  2. Two-factor authentication (2FA) using time-based one-time passwords (TOTP), compatible with Google Authenticator, Authy, 1Password, and Microsoft Authenticator. No external service, account, or API is required — TOTP runs entirely on your site.

WooCommerce is optional: install the plugin on any WordPress site to protect the core wp-login.php screens and add 2FA. When WooCommerce is present, the additional store forms become available automatically.

Features

  • WordPress Core Screen Protection: Scores the wp-login.php sign in, user registration, and lost password forms out of the box, with no WooCommerce required.
  • WooCommerce Support: When WooCommerce is active, also protects the customer Login, Registration, and Checkout forms — including the modern WooCommerce Checkout block (Store API), so stores built on the block editor are covered by the same reCAPTCHA scoring and Transaction Defense as the classic checkout.
  • Form Plugin Protection (Gravity Forms, Fluent Forms): Scores a form plugin's submissions here, so one reCAPTCHA implementation covers every form and payment on the site. Each form is classified from its stored definition — takes payment, creates an account, updates an account, changes a password — and gets its own score threshold and its own enforcement policy: a payment or signup form with no verification token is refused, an ordinary contact form is admitted and flagged. A coverage table shows, per form, whether a token field has actually been observed reaching it, what the form plugin's own captcha is still doing, and why the last submission was rejected. This plugin never writes to the form plugin's settings, so switching it off restores that plugin's own reCAPTCHA on the next request with nothing to re-enter. Forms using a visible challenge — reCAPTCHA v2 checkbox, hCaptcha, Turnstile — are left alone.
  • Transaction Defense (reCAPTCHA Enterprise): When using an Enterprise key, sends the order's billing/shipping address, amount, line items, and payment method with each checkout assessment to power Google's Fraud Prevention model, optionally blocks high-risk transactions, and annotates each order's outcome (legitimate/fraudulent) so the model keeps learning.
  • Account Defender (reCAPTCHA Enterprise): When using an Enterprise key, sends an anonymous, salted account identifier with each login, registration, and account-change assessment so Google's site-specific model can flag account takeovers, fake signups, and account farming. Gmail aliasing (dots and +tags) is collapsed before hashing, so one inbox registering under endless dot-variants maps to a single identifier. Logs the returned risk labels, optionally forces the two-factor challenge for enrolled users on suspicious logins, optionally rejects registrations Google labels as suspicious account creations, and annotates login, registration, two-factor, and account-modification outcomes to train the model: each scored sign-up is confirmed as legitimate on the account's first real login, or reported as fraudulent when the account is deleted before ever logging in (an operator sweeping spam accounts trains the model automatically). An optional setting additionally shares the account's email address with assessments for markedly better detection. Account changes are assessed, never blocked. Disabled by default.
  • Password Defense (reCAPTCHA Enterprise): When using an Enterprise key, checks a submitted username and password against Google's database of billions of breached credentials using a privacy-preserving protocol — the site sends only a 26-bit bucket prefix of a salted username hash and a locally EC-blinded hash of the password pair, so Google never sees the password and never learns the verdict. Runs after login in the background (never slows sign-in), at most once per user per week, and can optionally block a newly chosen password (reset, profile, WooCommerce account details) that is already known to be leaked, or refuse further sign-ins with a leaked password until it is changed. This is the plugin's answer to the Fraud Defense console's "Configure Password defense" recommendation, which otherwise has no PHP client. Requires the GMP or BCMath PHP extension. Disabled by default.
  • Email Alerts: Optionally email the site operator the moment Account Defender flags a suspicious login on an administrator account or a suspicious new-account sign-up, Transaction Defense blocks a high-risk checkout, or Password Defense finds leaked credentials — events that otherwise only reach the log. Built-in throttling (per-event dedupe plus an hourly cap with digest overflow) keeps a brute-force run or checkout-bot from turning into inbox spam; choose immediate, hourly-digest, or daily-digest delivery. Disabled by default.
  • Two-Factor Authentication (Google Authenticator): Users enrol from their profile by scanning a QR code (or entering the setup key manually) and confirming a code. A second-factor challenge is then required at login.
  • Backup Codes: Single-use recovery codes are generated at enrolment so users are never locked out if they lose their device.
  • Role-Based Enforcement: Optionally require 2FA for selected roles (e.g. Administrators). Administrators can reset another user's 2FA from the user-edit screen.
  • Application-Password Hardening: Optionally disable application passwords for users in a 2FA-enforced role, so a REST API or XML-RPC login can't bypass the second factor with a single credential. A per-account exemption list keeps deliberate integrations (e.g. an MCP server or backup tool on a dedicated service account) working. Off by default; fully reversible — turning it off restores existing application passwords without re-issuing them.
  • Site-Clone Protection for 2FA: A copied database (e.g. a staging clone) carries every enrolled user's authenticator secret with it, so without this the same code would work on both the original site and the copy. On by default, a secret enrolled on a different site no longer counts as active — the user signs in with their password and is prompted to re-enroll, giving each site its own secret.
  • MainWP Dashboard Management: With the companion "MainWP for Google Security for WordPress" extension on your MainWP dashboard, read and update each connected child site's Google Security settings from one screen. The child site only needs this plugin (version 2.9.0 or later) and the MainWP Child plugin; the connection travels over MainWP's own signed dashboard-to-child channel, not application passwords, and every setting is validated by the child exactly as it would be from the site's own settings screen.
  • Flexible Page-Specific Thresholds: Configure custom reCAPTCHA score thresholds individually for every protected form.
  • Seamless Upgrade: On activation, automatically imports the site keys and settings from the predecessor "Google reCAPTCHA v3 for WooCommerce" plugin, then deactivates and removes that old plugin.
  • Zero Overhead Frontend: Only loads JavaScript on active target pages to maintain optimal client-side page speed.

Installation

  1. Upload the plugin files to the /wp-content/plugins/google-security-for-wordpress directory, or install the plugin through the WordPress plugins screen directly.
  2. Activate the plugin.
  3. Navigate to Settings -> Google Security to configure your site keys, protected forms, and score thresholds.
  4. Navigate to Settings -> Two-Factor Auth to enable two-factor authentication and (optionally) require it for specific roles.
  5. Each user enables 2FA from Users -> Profile -> Two-Factor Authentication.

Frequently Asked Questions

Do I need a Google account or API for two-factor authentication?

No. "Google Authenticator" support means the industry-standard TOTP algorithm (RFC 6238). The codes are generated and verified entirely on your own site, and any compatible app (Google Authenticator, Authy, 1Password, Microsoft Authenticator) will work.

What happens if a user loses their phone?

They can use one of the single-use backup codes shown when they enrolled. An administrator can also reset a user's 2FA from the user-edit screen to restore access.

Does this work with WooCommerce checkout blocks?

Yes. Both the classic shortcode-based checkout and the modern WooCommerce Checkout block (which submits over the Store API) are protected by the same reCAPTCHA scoring and, with an Enterprise key, the same Transaction Defense.

What score threshold should I use?

We recommend a default threshold of 0.5. If you encounter spam submissions, increase the threshold closer to 1.0 (strict). If humans are blocked, lower it closer to 0.0 (lenient).

Why did 2FA reset after I cloned my site (e.g. to a staging subdomain)?

This is expected, and it's a security feature, not a bug. A TOTP code (the 6-digit number from your authenticator app) is generated from a secret stored in your site's database and the current time — nothing else. Cloning a site copies that database, including the enrolled secret, so without protection the same code would work on both the original site and the clone: an authenticator app has no way to know which one it's actually talking to. Google reCAPTCHA is unrelated to this — reCAPTCHA site keys and this behavior are two separate systems, and configuring reCAPTCHA to cover multiple domains does not affect it either way. By default, this plugin now stops that: an authenticator secret is bound to the site it was set up on, so a code that worked on the original stops working on a clone, and that user is prompted to set up 2FA again on the new site (which gets its own independent secret). This never locks anyone out — it simply falls back to a password sign-in and a re-enrollment prompt. You can turn this off ("Disable 2FA on cloned or moved sites" under Two-Factor Authentication) if you deliberately want a secret to keep working across multiple copies of a site, though we don't recommend it. Note that a database clone still copies password hashes and secrets regardless of this setting, so treat any staging/dev copy with the same care as production — restrict who can access it and consider it compromised if it's ever exposed.

Why does the Google Cloud console still recommend "Configure Account defense" when this plugin's Account Defender is on?

Because that recommendation is about a Google Cloud–side switch, not the plugin. This plugin implements the API side of Account Defender — it sends account identifiers with each assessment, reports account actions, and annotates outcomes so the model can learn. But the Account Defense feature itself must also be enabled on your reCAPTCHA key inside the Google Cloud console (Security → Fraud Defense → the "Configure Account defense" recommendation, or your key's settings). That step lives in your Google Cloud project and cannot be performed by a plugin. Until it is enabled, Google returns no account-risk labels, so the plugin's suspicious-login step-up, suspicious sign-up blocking, and related alerts have nothing to act on. After enabling it, also confirm in the plugin that the key type is Enterprise, Account Defender is switched on, and the login/registration form protections are enabled (that is where the assessments are made) — and expect labels to stay sparse for a while: the model is site-specific and needs to observe your traffic and annotations before it starts labelling.

Why does the Google Cloud console recommend "Configure Password defense" and how does this plugin answer it?

The Fraud Defense dashboard's "Detect leaked passwords" recommendation is a separate feature from Account Defender: it checks whether a user's exact username+password pair is already known to be breached, so a "correct" password can still be one credential-stuffing attempt away from an account takeover. Clicking "Configure Password defense" in the console only offers client helper libraries for Java and Node.js — PHP is left as "Other," with just the protocol documented, since WordPress plugins cannot assume a resident Node process on shared hosting. This plugin implements that protocol natively in PHP (Password Defense, under Enterprise Defense): the site sends only a 26-bit bucket prefix of a salted username hash and a locally EC-blinded hash of the password pair; Google re-encrypts it with its own key and returns candidate matches; the site strips its own blinding and compares locally, so Google never sees the password and never learns the verdict. It requires the GMP or BCMath PHP extension (for the elliptic-curve math) and an Enterprise key; unlike Account Defender there is no separate per-key console toggle to click — turning the plugin's setting on is the entire integration.

Can I manage these settings from MainWP?

Yes. Install the companion "MainWP for Google Security for WordPress" extension on your MainWP dashboard and keep this plugin (version 2.9.0 or later) on each child site, alongside the MainWP Child plugin. A per-site Google Security tab then loads and saves each child's settings from the dashboard. The connection uses MainWP's own signed dashboard-to-child channel — the same secure key handshake that powers a normal MainWP sync — not WordPress application passwords, so it is unaffected by the application-password hardening setting. Each child validates every incoming value exactly as it would from its own settings screen (thresholds are clamped, unknown roles and usernames are dropped, alert delivery reschedules), so a dashboard save can never write something the site would reject locally.

I enabled "Block application passwords for enforced roles" and my management tool lost connection. What now?

Turning the block on immediately stops existing application passwords from working for users in an enforced role — including any that a site-management, backup, or automation tool (or an MCP server) uses to reach your site over the REST API. Before enabling the block, either move those integrations onto an account whose role is not enforced, or add the account's username to the Exempt accounts list under Settings → Google Security → Two-Factor Authentication. Exempted accounts keep application-password access (they still face the interactive two-factor challenge on normal logins). If you have already been locked out of the settings screen, sign in interactively with your password and second factor and either add the exemption or switch the block off — nothing is deleted, so your existing application passwords start working again the moment you do. Standard MainWP Child connections use their own secure key handshake rather than application passwords, so they are unaffected by this setting. For the recommended long-term setup, see "How do I set up a service account for REST integrations?" below.

How do I set up a service account for REST integrations (MCP servers, backup and automation tools)?

Point integrations at a dedicated machine account and exempt only that account, so your own administrator login keeps zero single-factor paths. The exemption is per-account, not per-password — exempting your personal admin account would re-open the whole application-password channel on it, including passwords that don't exist yet. A dedicated account also gives you clean revocation (delete one password or account without touching human access) and clean attribution (the integration's actions appear under its own name in logs and revisions).

  1. Create the account: Users → Add New. Username like mytool-svc, an email alias you control, a generated password stored straight in your password manager (it is never typed again), role Administrator — or a trimmed custom role if the tool works with less.
  2. Optionally enrol it in 2FA, storing the TOTP secret in your password manager. The account never logs in interactively, but enrolment also activates this plugin's XML-RPC password block for it, sealing that path too.
  3. Exempt it first: Settings → Google Security → Two-Factor Authentication → Exempt accounts → add mytool-svc. Do this before creating the application password — once the block is on, the Application Passwords section is hidden for non-exempt accounts in enforced roles.
  4. Create the application password: Users → edit mytool-svc → Application Passwords. Name it after the consuming tool (e.g. "Novamira – office LLM") so the "Last Used" column stays meaningful, and copy the one-time password into the integration's configuration.
  5. Enable the policy: enforce the role, switch "Block application passwords for enforced roles" on, and make sure the Exempt accounts list contains only service accounts — no human logins.
  6. Cut over: test the integration, then revoke any application passwords remaining on human accounts (Users → Profile → Application Passwords → Revoke).
  7. Operate: one named application password per tool per site; review "Last Used" periodically; rotate on a schedule; on any incident, revoke that single password (or delete the service account) without disrupting anyone's normal access.

Changelog

2.30.1

  • Fixed: the Login/Signup Popup (Xootix) lost-password form rejected every reset with "Anti-spam verification token is missing." The hidden reCAPTCHA field was injected under the form's AJAX action name (lostPassword, the value of its _xoo_el_form hidden input) instead of the render slug the popup's xoo_el_form_end template action actually passes (lostpw, the basename of the plugin's lost-password section template), so the field never reached that form at all and validation then correctly refused the empty token. The field is now injected into the lostpw form and populated by the shared token bootstrap; a tokenless submission is still refused. The popup's login and registration forms and every other reset surface (wp-login.php, LoginPress, PowerPack, and the admin-initiated exemption) are unchanged.

2.30.0

  • Added: one-click toggles to trust the published edge addresses of Cloudflare and QUIC.cloud. When enabled, the plugin fetches each CDN's current IP list, stores it locally, and merges those ranges with any manually-declared trusted proxies. Lists refresh daily via WordPress cron and can be refreshed manually from the settings screen.
  • Added: bundled fallback ranges for Cloudflare and QUIC.cloud, used when a provider's public endpoint cannot be reached the first time a toggle is enabled, so coverage begins immediately.
  • Added: a "Known CDN proxies" section on the Alerts & Compatibility tab with toggles for Cloudflare and QUIC.cloud, loaded range counts, last refresh timestamp, and a manual refresh button.
  • Improved: sites that pass traffic through both Cloudflare and QUIC.cloud in series can enable both toggles together. The existing right-to-left forwarded-chain walker skips both trusted hops and resolves the real visitor address when X-Forwarded-For is used.

2.29.0

  • Fixed: on a site behind a CDN or reverse proxy, every reCAPTCHA assessment reported the proxy's address as the visitor's. The plugin read REMOTE_ADDR and nothing else, so a QUIC.cloud, Cloudflare or load-balanced site sent Google one datacenter address for every visitor — depressing scores site-wide, teaching Account Defender that the whole site shares one network, and putting the wrong address in security alert emails. A new trusted-proxy resolver reads the forwarded client address, but only when the request actually arrived from a proxy the operator has declared, and only by walking the forwarded chain from the end nearest the server. Any client can send X-Forwarded-For, so trusting it unconditionally would let an attacker launder a bad score by claiming a clean address; that is why the old code read REMOTE_ADDR, and why the default is an empty trusted list. Nothing changes on upgrade until proxies are configured in the new gswp_trusted_proxies option (or the filter of the same name), and gswp_client_ip_header selects the header for Cloudflare and Akamai style setups.
  • Added: a "Visitor IP address" section on the Alerts & Compatibility tab. It shows the address WordPress sees alongside the address actually sent to Google, so a proxy mismatch is visible at a glance rather than inferred from low scores, and provides the trusted-proxy list and header selector.
  • Added: when a request carries a forwarding header but no trusted proxies are configured, the plugin logs a throttled warning naming the address it is about to send Google. The symptom of getting this wrong — every visitor scoring low, for no visible reason — otherwise gives an operator nothing to search for.
  • Fixed: a WooCommerce checkout could submit a reCAPTCHA token that had already expired. Tokens live 120 seconds and are refreshed on a 100-second interval, but browsers suspend that timer for a hidden tab or a locked phone, so a customer who left the checkout page and came back could submit a token minutes old. The place-order guard caught a missing token but never a stale one; it now treats a token older than the refresh interval as missing, mints a fresh one, and resubmits. The token field is still never blanked, and a reCAPTCHA outage cannot trap the customer in a retry loop or a dead button — the submission proceeds and the server decides.
  • Fixed: the WooCommerce Checkout block did not refresh its token when the page became visible again, so the same stale-token submission was possible there. It now refreshes on tab focus and on back-button restore, matching the classic checkout.

2.28.0

  • Fixed: a WooCommerce checkout could be refused as suspected spam even when reCAPTCHA Transaction defense had scored the payment as low risk. reCAPTCHA returns two independent judgements — a general bot score for the browser, and (when transaction data is sent) a fraud probability for the specific payment. The bot score was checked first and rejected on its own, so a shopper on a VPN, a privacy extension or a corporate proxy could be turned away with a low score while Google's payment fraud model had already cleared the transaction. The fraud verdict now decides: when it is below the blocking threshold, the checkout proceeds and the override is logged. Automated card testing is unaffected — it scores low AND returns high transaction risk, so it is still refused. Applies to WooCommerce classic and block checkout and to Gravity Forms / Fluent Forms payment forms. Sites that prefer the old behaviour can return false from the new gswp_defer_score_to_fraud_verdict filter.
  • Changed: the low-score rejection message no longer tells the visitor they look like spam. A low score is frequently a VPN, a locked phone or a stale page rather than an attacker, and a paying customer should not be accused of one. The message now reads "This submission did not pass our automated security check. Please refresh the page and try again, or contact us if the problem continues." The operator-facing log still records the exact score and threshold, so nothing is lost for diagnosis. Also applies to Gravity Forms and Fluent Forms rejections.

2.27.3

  • Fixed: admin-initiated password resets failed with "Anti-spam verification token is missing" on the user profile "Send Reset Link" button, and with "Password reset links sent to 0 users" from the Users screen row action and bulk action. Admin-initiated resets are now exempt from reCAPTCHA token enforcement. They are performed by an authenticated administrator and are already protected by a capability check and a WordPress nonce, both verified by WordPress core; none of the three entry points can carry a reCAPTCHA token, so enforcing one only ever blocked a legitimate action. The public lost-password form on wp-login.php is unchanged and remains fully protected.
  • Removed: the wp-admin reCAPTCHA script loading, hidden token fields and XMLHttpRequest patching added in 2.26.2–2.27.2 for those paths. wp-admin no longer loads reCAPTCHA on the Users or profile screens.
  • Hardened: the request-scoped token cache added in 2.27.2 was keyed on the token alone, so within one request a verdict earned under one threshold context could satisfy a stricter one without the action or threshold being re-checked. The key now includes the context and expected action.

2.27.2

  • Fixed: the admin "Send Reset Link" button could still fail with an expired or missing token. The admin inline script is rewritten in vanilla JS with grecaptcha readiness polling and XMLHttpRequest.prototype.send patching, so every send_password_reset AJAX request mints a fresh single-use token before it leaves the browser.
  • Fixed: bulk "Send password reset" on the Users screen rejected all but the first selected user with "Anti-spam verification expired" because the same single-use token was verified once per user. A request-scoped token cache in the verifier now returns the first user's verdict for subsequent users sharing the same token, preventing Google's DUPE rejection.

2.27.1

  • Fixed: front-end PowerPack lost-password forms still failed with an "Anti-spam verification expired" error because the single-use Enterprise token was verified twice in one AJAX request. Removed the redundant wp_ajax_pp_lf_process_lost_pass guard; validation now runs only on the lostpassword_post action fired by PowerPack's retrieve_password() call.
  • Fixed: the WordPress admin "Send Reset Link" button had no populated reCAPTCHA token because the shared token-refresh bootstrap was only printed on the front-end footer. The bootstrap is now also printed on admin_print_footer_scripts, and the admin inline script fetches an initial token plus refreshes on the Send Reset Link click.

2.27.0

  • Fixed: front-end PowerPack lost-password forms and the WordPress admin "Send Reset Link" button could not send password reset emails when lost password form protection was enabled. Both paths submit via AJAX without serializing the reCAPTCHA token field, so the token was either missing or already spent on retry. The PowerPack inline prefilter now covers pp_lf_process_lost_pass, and a new admin prefilter covers WordPress core's send_password_reset action; each appends a fresh token at send time and refreshes it after a failed attempt.

2.26.2

  • Fixed: password reset links could not be sent from the WordPress admin when the lost password form protection was enabled. The reCAPTCHA script and token field are now loaded on the Users screen and the user profile screens, so admin-initiated resets pass verification. The per-user "Send password reset" row action remains a nonce-protected link and is not blocked by reCAPTCHA.

2.26.0

  • Added: Account Defender risk assessment on the lost password form. When Account Defender flags a password reset request as suspicious login activity, the plugin can now optionally block the reset email from being sent (new "Block suspicious reset requests" toggle under Account Defender). Off by default: flagged requests are logged and alerted but never blocked, so a legitimate user is not locked out of recovery.
  • Added: reCAPTCHA v3 scoring on WordPress core comment forms. A new "WordPress Comments" toggle under Form Protection scores comment submissions to block spam and automated link-dropping. Trackbacks, pingbacks, and users with moderate_comments capability are exempt.
  • Added: alert integration for suspicious lost password requests. Reuses the existing login alert toggle and throttling pipeline.

2.25.0

  • Added: PowerPack module protection extended to the Contact Form and Subscribe Form (newsletter signup) modules, reaching parity with the Beaver Builder core module protection added in 2.24.0. The PowerPack Login Form and Registration Form were already covered.
  • Added: new "PowerPack Forms" section on the Form Protection settings tab with independent enable/threshold toggles for the Contact Form and Subscribe Form modules.
  • Added: the module's own built-in captcha (reCAPTCHA v2, hCaptcha, and — on the Contact Form — Cloudflare Turnstile) is automatically stripped when this plugin's protection is active, preventing dual-captcha conflicts.
  • Note: like the Beaver Builder core modules, both PowerPack modules build their AJAX payloads manually rather than serializing the form, so a small inline script appends the token via $.ajaxPrefilter. Inert unless PowerPack is active.

2.24.0

  • Added: Beaver Builder core module protection. The Login Form, Contact Form, and Subscribe Form (newsletter signup) modules rendered by Beaver Builder core are now protected with reCAPTCHA v3 scoring, following the same pattern as the PowerPack integration.
  • Added: new "Beaver Builder Forms" section on the Form Protection settings tab with independent enable/threshold toggles for the Contact Form and Subscribe Form modules. The Login Form module reuses the existing WordPress Login toggle.
  • Added: the module's own built-in reCAPTCHA (Contact Form and Subscribe Form) is automatically stripped when this plugin's protection is active, preventing dual-captcha conflicts.
  • Note: BB core modules build their AJAX payloads manually rather than serializing the form, so a small inline script appends the token via $.ajaxPrefilter. Inert unless Beaver Builder is active.

2.23.2

  • Fixed: a Fluent Forms profile-edit form ("User Update") was classified the same as a signup form and rejected a submission with a missing or stale token, telling a signed-in visitor their own profile edit could not be verified. Fluent Forms Pro stores both signup and profile-update feeds under one internal record, and this plugin was reading it wrong on every install. It now reads the same field Fluent Forms Pro itself uses to tell the two apart, confirmed against Fluent Forms Pro's source directly. This is the highest-impact fix in this release; Fluent Forms support remains switched off by default until you turn it on.
  • Fixed: a Transaction Defense annotation (marking a Fluent Forms payment as legitimate or fraudulent after the fact, for reCAPTCHA Enterprise sites with that feature on) never actually ran — the two pieces of information it needed were being read from the wrong positions, silently. It now reads correctly.
  • Fixed: a Fluent Forms rejection message could be delivered to the browser and never appear on screen, leaving the visitor watching a stopped submit button with no explanation. It's now shown the same way Fluent Forms shows its own site-wide messages (like "too many attempts"), which is guaranteed visible.
  • Fixed: a site with reCAPTCHA v2 keys saved in Fluent Forms — a very common configuration, even if v2 isn't actually used on any form — was previously reported as ineligible for coverage on every single Fluent Forms form, with no visible reason. Coverage is now based on whether Fluent Forms will actually validate a challenge on that specific form, which can be confirmed directly rather than assumed from a global setting.
  • Fixed: Fluent Forms conversational forms (the one-question-at-a-time style) were reported as a form this plugin covers, then flagged with a coverage-gap warning on every single submission forever, because they render through a path this plugin cannot inject into. They are now correctly reported as unsupported instead of alarming continuously about a gap that can never close.
  • Changed: high-risk Fluent Forms transactions can now be blocked by default when Transaction Defense is on, rather than only scored and logged. Every payment method Fluent Forms ships (Stripe, Square, PayPal, and others) was confirmed not to charge or place a hold on a card before this plugin's check runs, so a block leaves nothing pending on the customer's card. This was verified against Fluent Forms' and Fluent Forms Pro's source for every gateway they ship, and confirmed on a live Stripe account: a refused submission left no charge, no hold, and no incomplete payment in the Stripe dashboard. If your site uses a non-standard or add-on payment gateway, you can turn blocking back off with a filter — see the Form Protection screen.
  • Changed: this plugin's own field is now registered with Fluent Forms as an expected submission value, rather than being recovered by re-reading the raw request. This is a more direct integration with how Fluent Forms is actually built, confirmed against Fluent Forms' source.
  • Fixed: three Fluent Forms payment field types (Stripe's and Square's card entry, and the coupon field) were not recognised by this plugin's payment-detection fallback.
  • Fixed: the Form Protection screen's "this plugin is handling reCAPTCHA" toggle, and the whole coverage table beneath it, could show the wrong state for up to a page reload after saving — most visibly, switching a form plugin on and saving could make the toggle appear to switch itself back off. The setting was saved correctly; the display was reading a snapshot taken when the page first loaded instead of the response from the save itself. If you saved this toggle before updating past this point and it looked like it reverted, check its actual state after reloading the page — it may already be correct.
  • Note: every fix in this release was confirmed by reading Fluent Forms' and Fluent Forms Pro's own source code directly, resolving open questions from 2.23.0 and 2.23.1 that could not be settled from documentation or a single live site.

2.23.1

  • Fixed: this plugin could not read Fluent Forms settings that Fluent Forms stores as an object rather than a list — and on Fluent Forms 6.2.9 at least one is. Any such setting read as "not present". If your reCAPTCHA configuration is one of them, the Form Protection table would have reported Fluent Forms' own captcha as "unknown" while it was configured and running, and treated those forms as ours to take over. Found by the verification suite on a live install, before the provider had been switched on anywhere.
  • Fixed: the payment currency sent with a Fluent Forms fraud assessment is now read from the site-wide payment settings, not only from per-form settings. Currency is configured globally on most sites, so the previous lookup found nothing and the assessment went without it.
  • Fixed: three of the Fluent Forms verification scripts. The captcha inspector (chunk 23) crashed outright on the object-typed setting above; the render check (chunk 21) attributed one hook's activity to a non-existent "form #0"; and the submission capture (chunk 22) did not say that the provider has to be switched on first, which made a correct run look like a failure.

2.23.0

  • Added: Fluent Forms support. This plugin can now score Fluent Forms submissions the same way it scores Gravity Forms ones — per-form classification, its own score thresholds, transaction data on payment forms, and the same coverage reporting. As always, it never writes to Fluent Forms' settings: turning it off restores Fluent Forms' own captcha on the next request, with nothing to re-enter.
  • Note: unlike Gravity Forms, Fluent Forms support is not switched on automatically when you upgrade. It ships off, and the Form Protection table shows you every form and how it has been classified before anything intercepts a submission. Turn it on when the table looks right. This is deliberate: the integration was built against Fluent Forms' published developer documentation rather than a live install, and switching it on for everybody before it has been confirmed in the field is not a trade worth making.
  • Added: forms using hCaptcha or Cloudflare Turnstile are now recognised as such and left alone. This plugin scores invisibly and has no equivalent of a visible challenge, so those forms keep the captcha they already have. Previously the Form Protection table had no way to describe a captcha that was not Google's, and would have reported one as "v2 checkbox" — sending you looking for reCAPTCHA settings that do not exist on your site.
  • Added: a "Changes a password" score threshold, for both Gravity Forms and Fluent Forms. The setting has existed since 2.22.0 but was never shown, so the one class of form most worth tuning could only be reached through the database.
  • Note on Fluent Forms payments: high-risk transactions are scored, logged and reported back to Google, but are not blocked. Whether Fluent Forms authorises the card in the browser before the submission reaches this plugin has not been confirmed; if it does, blocking would refuse the order without releasing the hold on the customer's card. Blocking can be enabled per site once you have confirmed the behaviour of your gateway — see tests/manual/README-ff-replacement.md.

2.22.1

  • Fixed: after any rejected submission, the next attempt on the same form was itself rejected with "Anti-spam verification expired" — no matter what the visitor did. reCAPTCHA tokens can only be used once, and a form that submits without reloading the page (an AJAX login form, or a Gravity Form) kept resubmitting the spent one. The only thing that eventually cleared it was a background refresh up to 100 seconds later, so the form appeared to fix itself with no explanation. A token is now replaced the moment a submission spends it. Most visible on login forms, where a single mistyped password locked the visitor out of retrying; on a Gravity Forms payment form it meant a customer whose card was declined could be blocked from trying again.
  • Fixed: returning to a form with the browser's Back button could reject the next submission. The restored page carried the token it was frozen with, which had usually expired. Tokens are now refreshed when a page is restored from the back/forward cache.

2.22.0

  • Fixed: Gravity Forms forms that create or update a WordPress account rejected every submission, from every visitor, with "Verification failed. You have been flagged as potential spam." Nobody was flagged as spam. This plugin decided the reCAPTCHA action twice — once when placing the hidden token field in the form, once when checking the submission — and the two disagreed for account forms, so reCAPTCHA Enterprise rejected the token on a name mismatch before it ever looked at the score. The two decisions now come from one place and cannot drift apart again. If you use Gravity Forms with the User Registration add-on and Enterprise keys, this affected every submission of those forms.
  • Fixed: a rejected submission no longer accuses the visitor of being spam when the cause has nothing to do with them. An expired or already-used token, a browser that could not produce one, and a token minted against a different site key are all site or client problems, and each now says so. The only message that still mentions spam is the one for a genuinely low reCAPTCHA score.
  • Fixed: on classic (non-Enterprise) keys, an expired or reused token was reported as spam. reCAPTCHA tokens last two minutes and can only be used once, so a visitor filling a long form on a phone hit this without doing anything wrong. It now asks them to try again, mat

This README is longer than the copy stored here. Read the rest on GitHub →