hryvinskyi / magento2-invisible-captcha

hryvinskyi/magento2-invisible-captcha

Magento 2 Google Invisible Captcha (Recaptcha) module

magento2-module Compatibility: Not yet tested Code Quality: Pending Tests: N/A Security: Pending MIT
Viewing version 3.0.3. Latest version is 3.1.0. Switch to latest

Hryvinskyi_InvisibleCaptcha

Multi-provider invisible captcha and bot protection for Magento 2.

Version 3 merges the former Hryvinskyi_TurnstileProtection module into a single,
provider-agnostic extension that protects both individual forms and whole
routes
, using whichever CAPTCHA provider you configure.

Providers

Code Provider Mode
recaptcha_v2_checkbox Google reCAPTCHA v2 — "I'm not a robot" visible checkbox
recaptcha_v2_invisible Google reCAPTCHA v2 — invisible badge invisible
recaptcha_v3 Google reCAPTCHA v3 invisible, score-based
recaptcha_enterprise Google reCAPTCHA Enterprise invisible, score-based (assessments API)
turnstile Cloudflare Turnstile managed / invisible

Capabilities

  • Form protection — invisibly protects the same surface as Magento's native
    reCAPTCHA suite: customer login (incl. AJAX popup & checkout), register,
    forgot password, account edit, contact, newsletter,
    send-to-friend, product review, share wishlist, apply coupon
    (cart), PayPal Payflow Pro, and the admin login / forgot-password
    forms. Each is toggled independently; score-based providers also support a
    per-form threshold.
  • WebAPI / headless protection — REST and GraphQL validation for
    place order, apply coupon (checkout), in-store pickup place order,
    and resend confirmation email. Protected endpoints require the captcha
    token in the X-Captcha-Token request header (X-ReCaptcha also accepted).
    A hryvinskyiInvisibleCaptchaConfig(formType:) GraphQL query exposes the
    active provider's client config for headless storefronts. Endpoint coverage is
    extensible via Api\Webapi\WebapiConfigProviderInterface.
  • Route protection — a full-page interstitial challenge for requests that
    match a Cloudflare-style rule expression (field / operator / value rows joined
    by AND/OR). A verified visitor receives an HMAC-signed cookie and is keyed into
    a separate Varnish cache bucket via the HTTP context. An optional fallback
    provider
    is revealed on the challenge page after a delay. The interstitial is
    a self-contained, system-font page (no external font/CDN requests) whose accent
    color is themeable from the admin.

Installation

composer require hryvinskyi/magento2-invisible-captcha
bin/magento module:enable Hryvinskyi_InvisibleCaptcha
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy

Dependencies: hryvinskyi/magento2-base, hryvinskyi/magento2-theme-assets,
symfony/http-client + PSR-7/17/18.

Configuration

Stores → Configuration → Hryvinskyi → Invisible Captcha & Bot Protection
(hryvinskyi_invisible_captcha/*):

  • General — master switch, active provider, lazy-load, disable-submit, debug.
  • Provider Credentials — site/secret keys (+ Enterprise project id, widget
    options) per provider. Secret keys are encrypted and flagged sensitive.
  • Form Protection — per-form toggles and score thresholds (storefront + admin).
  • Route Protection — rules editor, route-gate provider override, fallback
    provider, cookie lifetime, IP / user-agent exclusions, AJAX-marker params, etc.
    Includes a Challenge Page Appearance sub-group that themes the interstitial
    accent palette — primary_color (#2f6bd8), primary_color_deep (#2557b6) and
    primary_color_soft (rgba(47,107,216,0.12)) under
    hryvinskyi_invisible_captcha/route_protection/appearance/*, injected as
    CSP-safe --primary* overrides and configurable per store view.
  • Advanced — outbound verification HTTP timeout.

Architecture (extension points)

  • Api\Provider\ProviderInterface + ProviderPoolInterface — add a provider by
    implementing the interface and adding it to the Model\Provider\Pool DI array.
  • Api\Verification\VerifierInterface / VerificationRequestInterface /
    VerificationResultInterface — provider-agnostic verification.
  • Api\Filter\FieldProviderInterface / OperatorProviderInterface — extend the
    route rule engine with custom fields/operators via DI arrays.
  • Model\Strategy\{Token,Failure}\* — pluggable token extraction & failure handling.

WebAPI / checkout note

The place_order, coupon_code (checkout), store_pickup and
resend_confirmation_email keys validate server-side on the WebAPI/GraphQL
call. They are off by default. Before enabling one, make sure your storefront
/ headless client sends the captcha token in the X-Captcha-Token header on that
call (for score-based providers, execute the provider with the matching action,
e.g. place_order) — otherwise those requests will be rejected. The token for
form-level (non-WebAPI) submissions is handled automatically via the hidden
hryvinskyi_invisible_token field.

Verify endpoint & caching

The route-gate challenge POSTs to invisiblecaptcha/verify. On success it sets the
hryvinskyi_captcha_verified cookie and the CAPTCHA_VERIFIED HTTP-context vary
key. After deploying v3, run a full page cache flush (the vary key / cookie /
X-InvisibleCaptcha-Challenge header were renamed from the old Turnstile names).

Upgrading from v2.x / TurnstileProtection

Run setup:upgrade. The data patch MigrateLegacyCaptchaConfig copies your old
hryvinskyi_invisible_captcha/* (reCAPTCHA v3) and hryvinskyi_turnstile/*
settings into the new tree (encrypting the legacy plaintext v3 secret). The
standalone Hryvinskyi_TurnstileProtection module is removed — disable it after
upgrading.

Migrating from Magento's native Google reCAPTCHA

If the store already uses Magento's built-in Google reCAPTCHA (Magento_ReCaptcha*),
import its configuration into this module with:

bin/magento hryvinskyi:invisible-captcha:migrate-recaptcha --dry-run   # preview the change set
bin/magento hryvinskyi:invisible-captcha:migrate-recaptcha             # apply

The command reads every recaptcha_frontend/* and recaptcha_backend/* row from
core_config_data (all scopes) and writes the equivalent
hryvinskyi_invisible_captcha/* settings:

  • Credentials — v3, v2-checkbox and v2-invisible site/secret keys map to the
    matching provider under Provider Credentials. The native module stores both
    keys encrypted; the secret key is copied verbatim (this module's field is
    encrypted too, so the ciphertext stays valid under the same crypt key), while the
    site key is decrypted on copy because this module stores it in plain text.
    Run the command on the installation that owns the crypt key. Frontend keys win
    when frontend and backend differ.
  • Per-form selectors — each type_for/<form> that isn't "disabled" turns on the
    corresponding form under Form Protection (the native place_order gate enables
    both checkout and in-store pickup here). The v3 section-wide score threshold is
    fanned out to each enabled v3 form.
  • Derived — the active provider (most-used across the enabled forms), the master
    switch and the form-protection switches are set so protection stays live.
  • Status hand-over — after copying, each migrated native type_for/* selector
    row is cleared, so the built-in reCAPTCHA stops challenging those forms and
    this module takes over in the same run (source_disabled in the summary table).
    Native credentials are left in place, so the switch is easy to revert.

Existing values in the target tree are never overwritten unless you pass --force.
The command flushes the config cache and prints a per-path summary table.

Upgrading from a version whose migration copied the site key without decrypting
it? Re-run with --force to overwrite the broken ciphertext values.

skipped_undecryptable rows / missing site keys? The installation's crypt key
(app/etc/env.php) cannot decrypt those values — typical when the database was
imported from another environment without its crypt key. Nothing else encrypted
(payment keys, SMTP passwords) will decrypt on such an installation either.
Restore the original crypt key (all versions, in order) or re-enter the keys in
the admin, then re-run the command.

Because the hand-over disables the native reCAPTCHA at config level, physically
removing its modules is optional. If you still want them gone, the companion
metapackage hryvinskyi/magento2-invisible-captcha-recaptcha-replace replaces
every magento/module-re-captcha-* package (Two-Factor Auth and security.txt
from magento/security-package are left untouched). Caution: other installed
code may compile against the reCAPTCHA API modules — notably the bundled
PayPal Braintree module implements
Magento\ReCaptchaWebapiApi\Api\WebapiValidationConfigProviderInterface, so
removing all 25 packages breaks setup:di:compile on standard installs. On such
stores prefer the config-level disable above.

CLI

bin/magento hryvinskyi:invisible-captcha:disable [global|frontend|adminhtml] [--website_id=N]
bin/magento hryvinskyi:invisible-captcha:migrate-recaptcha [--dry-run] [--force]

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.

[3.0.3] - 2026-07-08

Fixed

  • Route-protection regex rules accept bare (Cloudflare-style) patterns. The
    regex / not_regex operators passed the admin value straight to
    preg_match(), which requires PCRE delimiters — so a natural pattern like
    .* or ^catalog_.* errored out ("invalid regex skipped" in the log) and the
    rule never fired. Values are now tried as delimited PCRE first (modifiers like
    ~…~i keep working) and auto-wrapped in ~…~ on a parse error; only patterns
    invalid in both forms are skipped (still fail-safe, still logged).

Changed

  • migrate-recaptcha: undecryptable values are reported, not silently skipped.
    When a native encrypted value cannot be decrypted with this installation's crypt
    key (e.g. a DB imported from another environment), the row now appears in the
    change table with the new skipped_undecryptable status and the command prints
    a crypt-key warning with remediation steps, instead of omitting the row without
    a trace.

[3.0.2] - 2026-07-08

Fixed

  • Login-failure redirect never used the before-auth URL. RedirectUrl\BeforeAuthUrl
    was typed against SessionManagerInterface, whose global preference is
    Session\Generic — a session object with the default storage namespace, while
    before_auth_url is written into the customer namespace. The provider therefore
    always fell back to the login URL. It now depends on Magento\Customer\Model\Session
    (Proxy-wired in etc/frontend/di.xml), so a failed login challenge redirects back
    to the customer's intended page again.
  • Unit test suite runs green end-to-end on PHP 8.5 / PHPUnit 10 (598 tests): fixed a
    by-reference capture bug in RouteGateTest, seeded the global ObjectManager for the
    backend block in ProtectionRulesTest, avoided the undefined BP constant in
    DebugTest, and re-pointed BeforeAuthUrlTest at the concrete customer session
    (mocking the magic accessor on the interface fatally failed under PHPUnit 10).

[3.0.1] - 2026-07-08

Fixed

  • migrate-recaptcha: native site key was copied as ciphertext. Magento's
    native reCAPTCHA stores public_key through Config\Backend\Encrypted, while
    this module keeps site_key in plain text — the migration now decrypts values
    carrying the Magento crypt envelope (N:N:…) and passes legacy plaintext keys
    through untouched; undecryptable values are skipped instead of written. Stores
    that migrated with the broken version can repair by re-running with --force.

Added

  • migrate-recaptcha: status hand-over. After copying, the command clears
    every migrated native type_for/* selector row (new source_disabled change
    status, previewed in --dry-run), so the built-in reCAPTCHA is disabled and
    this module is enabled in one run. Native credentials are left in place;
    physically removing the native modules becomes optional — which also avoids
    setup:di:compile breakage on installs whose bundled code (e.g. PayPal
    Braintree) compiles against the reCAPTCHA API modules.

[3.0.0] - 2026-07-08

Major rewrite into a multi-provider captcha & bot-protection module. The
former Hryvinskyi_TurnstileProtection module is merged in and removed.

Added

  • Five providers behind one abstraction (Api\Provider\ProviderInterface +
    ProviderPoolInterface): Google reCAPTCHA v2 checkbox, v2 invisible, v3,
    reCAPTCHA Enterprise, and Cloudflare Turnstile. Pick the active provider in
    Stores → Configuration → Hryvinskyi → Invisible Captcha & Bot Protection.

  • Route-level protection (capability B, from TurnstileProtection): a
    site-wide interstitial challenge driven by a Cloudflare-style rule engine
    (Model\Filter\*), an HMAC-signed verified cookie, Varnish HTTP-context vary,
    and a verify endpoint at invisiblecaptcha/verify.

  • reCAPTCHA Enterprise verifier using the projects/{id}/assessments API.

  • Bounded PSR-18 HTTP transport (Model\Verification\Http\BoundedHttpClient)
    with a configurable wall-clock budget; the verifier fails closed and fast.

  • Per-provider client JS strategies (view/base/web/js/provider/*) so any
    provider/widget type renders from one form component.

  • Provider-independent verified-cookie HMAC key (Magento crypt key) so switching
    providers does not invalidate verified sessions.

  • Data patch Setup\Patch\Data\MigrateLegacyCaptchaConfig migrating both the
    legacy hryvinskyi_invisible_captcha/* (v2.x) and hryvinskyi_turnstile/*
    settings into the unified v3 config tree.

  • Configurable challenge accent color. New Challenge Page Appearance
    sub-group under Route Protection
    (hryvinskyi_invisible_captcha/route_protection/appearance/*) exposes
    --primary (#2f6bd8), --primary-deep (#2557b6) and --primary-soft
    (rgba(47,107,216,0.12)). ChallengeRenderer injects them as a CSP-safe
    :root override (values sanitized against CSS-injection) so the interstitial
    re-themes per store view.

  • Migration from Magento's native Google reCAPTCHA. New CLI command
    hryvinskyi:invisible-captcha:migrate-recaptcha [--dry-run] [--force] imports
    recaptcha_frontend/* / recaptcha_backend/* config (all scopes) into the
    hryvinskyi_invisible_captcha/* tree: provider credentials (secrets copied
    verbatim), per-form type_for/* selectors, the v3 score threshold, and the
    derived active provider + master switches. The migrator holds only mapping
    policy (Model\Migration\RecaptchaMigrator) behind
    Api\Migration\RecaptchaMigratorInterface, reads/writes core_config_data
    through the Api\Migration\CoreConfigGatewayInterface port, carries per-run
    state in Model\Migration\MigrationRun, and returns typed
    Model\Migration\ChangeRecord results; never clobbers existing values
    without --force.

  • Companion metapackage hryvinskyi/magento2-invisible-captcha-recaptcha-replace
    (published separately) that replaces every magento/module-re-captcha-*
    package to remove the native reCAPTCHA while leaving the rest of
    magento/security-package intact.

Changed

  • Unified, snake-case config tree under hryvinskyi_invisible_captcha/{general, providers,form_protection,route_protection,advanced}.

  • The form-verify flow is now provider-agnostic: one rich
    Api\Verification\VerificationResultInterface replaces the reCAPTCHA-only
    response; the "Provider" namespace now means a captcha vendor, while failure /
    token / redirect strategies moved to Model\Strategy\*.

  • Score thresholds apply only to score-based providers (v3 / Enterprise).

  • CSP whitelist is now global and covers Cloudflare + Google hosts across
    script/frame/connect/form-action.

  • Submit gating ("disable submit until ready") is now done with CSS + a small
    parse-time inline script rendered next to the captcha marker, instead of
    rewriting the whole HTML response server-side. The script adds the
    hryvinskyi-captcha-disabled-submit class to the surrounding form (the CSS in
    _module.less makes the submit button unclickable while that class is
    present); form-manager.js removes the class once the captcha is ready.

  • Redesigned route-gate challenge page. The inline interstitial
    (view/frontend/web/inline_challenge/) was reworked into a clean,
    system-font layout and no longer loads Google Fonts (Inter / Cormorant
    Garamond) — one fewer third-party request and a tighter CSP surface. The
    placeholder contract and script.js bootstrap are unchanged.

Removed / BREAKING

  • Hryvinskyi_TurnstileProtection is merged in and deleted. Its config under
    hryvinskyi_turnstile/* is migrated automatically; external references to the
    old turnstile/verify route, X-Turnstile-Challenge header,
    hryvinskyi_turnstile_verified cookie and TURNSTILE_VERIFIED vary key are
    renamed (run a one-time full-page-cache flush after deploy).
  • Legacy v2.x classes (Helper\Config\*, Model\ReCaptcha\*, Model\Verify\*,
    Model\Provider\{Failure,TokenResponse}*, Observer\Captcha, LayoutSettings)
    are removed; integrators should target the new Api\* contracts.
  • Requires hryvinskyi/magento2-theme-assets (replaces the previous
    asset-renderer dependency).
  • Removed the server-side Plugin\DisableSubmit response rewriter and dropped
    the voku/simple_html_dom, symfony/dom-crawler and ext-dom requirements
    it needed; submit gating is now handled in CSS + JS (see above).

[2.5.5] - 2026-06-01

Fixed

  • PHP 8.5: added the : int return type and an explicit return Command::SUCCESS;
    to Command\Captcha::execute(), matching Symfony Console Command::execute(): int
    (fixes the "must be compatible" fatal error).
  • Monolog 3: Model\Debug::write() now accepts Monolog\LogRecord instead of array
    to match Magento\Framework\Logger\Handler\Base::write().

2.5.4 - 2026-06-01

Added

  • PHP 8.5 support (~8.5 added to the php requirement).

Fixed

  • PHP 8.4/8.5 compatibility: replaced implicit-nullable parameter declarations
    (Type $param = null) with explicit nullable types (?Type $param = null)
    across the failure providers, area config, and abstract config classes, removing
    the "Implicitly marking parameter as nullable is deprecated" notices.

Security

  • Widened the symfony/dom-crawler requirement from >=2.7 <5.0 to
    ^5.4.52 || ^6.4.40 || ^7.4.12 || ^8.0.12. The previous constraint could only
    resolve to versions affected by CVE-2026-45071 (XXE / local file disclosure
    in DomCrawler::addXmlContent() via validateOnParse = true), because no fix
    exists for the 2.x–4.x branches. The new constraint pins to the patched releases
    and aligns with the Symfony components shipped by Magento 2.4.x.

2.5.3 - 2025-10-06

Changed

  • Updated dependencies and removed leftover console.log debug output.

2.5.2 - 2025-08-12

Changed

  • composer.json maintenance.

2.5.1 - 2025-06-24

Changed

  • composer.json maintenance.

2.5.0 - 2025-06-17

Changed

  • composer.json maintenance.
Versions
Version Stability QA Status Compatibility Released
3.1.0 stable Fail Magento 2.4.8-2.4.9 Details 2026-07-11 08:35:50
3.0.4 stable Fail Magento 2.4.8-2.4.9 Details 2026-07-08 17:32:52
3.0.3 stable Not tested Not yet tested Details 2026-07-08 17:00:33
3.0.2 stable Not tested Not yet tested Details 2026-07-08 16:06:56
3.0.1 stable Not tested Not yet tested Details 2026-07-08 15:21:10
3.0.0 stable Not tested Not yet tested Details 2026-07-08 14:14:55
2.5.5 stable Fail Magento 2.4.8 Details 2026-06-01 19:51:18
2.5.4 stable Not tested Not yet tested Details 2026-06-01 12:25:56
2.5.3 stable Not tested Not yet tested Details 2025-11-05 18:06:24
2.5.2 stable Not tested Not yet tested Details 2025-08-12 09:01:06
2.5.1 stable Not tested Not yet tested Details 2025-06-24 09:54:36
2.5.0 stable Not tested Not yet tested Details 2025-06-16 22:29:28
2.4.10 stable Not tested Not yet tested Details 2025-06-16 22:27:23
2.4.9 stable Not tested Not yet tested Details 2025-05-05 18:55:28
2.4.8.1 stable Not tested Not yet tested Details 2025-05-05 13:18:08
2.4.8 stable Not tested Not yet tested Details 2024-03-28 20:23:25
2.4.7 stable Not tested Not yet tested Details 2024-01-18 12:29:05
2.4.6 stable Not tested Not yet tested Details 2024-01-18 11:31:05
2.4.5 stable Not tested Not yet tested Details 2023-12-13 10:56:58
2.4.5-dev dev Not tested Not yet tested Details 2022-09-01 07:46:00
2.4.4 stable Not tested Not yet tested Details 2022-06-23 09:59:18
2.4.3 stable Not tested Not yet tested Details 2022-06-21 12:26:03
2.4.2 stable Not tested Not yet tested Details 2022-03-22 10:43:26
2.4.1 stable Not tested Not yet tested Details 2022-03-22 10:34:54
2.4 stable Not tested Not yet tested Details 2022-03-22 10:29:50
2.3.2 stable Not tested Not yet tested Details 2021-06-19 08:57:54
2.3.1 stable Not tested Not yet tested Details 2020-09-23 22:33:58
2.3.0 stable Not tested Not yet tested Details 2020-09-07 14:03:20
2.2.7 stable Not tested Not yet tested Details 2020-08-13 19:20:15
2.2.6 stable Not tested Not yet tested Details 2020-08-07 20:19:11
2.2.5 stable Not tested Not yet tested Details 2020-08-03 19:09:47
2.2.4 stable Not tested Not yet tested Details 2020-06-16 20:43:55
2.2.3 stable Not tested Not yet tested Details 2020-06-16 20:35:41
2.2.2 stable Not tested Not yet tested Details 2020-06-16 19:32:58
2.2.1 stable Not tested Not yet tested Details 2020-06-16 17:29:41
2.2.0 stable Not tested Not yet tested Details 2020-06-16 11:40:32
2.1.10.1 stable Not tested Not yet tested Details 2020-06-09 13:23:05
2.1.10 stable Not tested Not yet tested Details 2020-05-21 17:55:11
2.1.9.1 stable Not tested Not yet tested Details 2020-02-12 21:05:09
2.1.9 stable Not tested Not yet tested Details 2020-02-09 20:23:41
2.1.8 stable Not tested Not yet tested Details 2020-02-05 13:28:44
2.1.7.1 stable Not tested Not yet tested Details 2020-01-30 09:31:19
2.1.7 stable Not tested Not yet tested Details 2020-01-10 13:06:04
2.1.6 stable Not tested Not yet tested Details 2019-10-20 10:46:16
2.1.5 stable Not tested Not yet tested Details 2019-09-25 13:33:54
2.1.4 stable Not tested Not yet tested Details 2019-09-24 20:37:24
2.1.3 stable Not tested Not yet tested Details 2019-09-17 12:16:12
2.1.2 stable Not tested Not yet tested Details 2019-09-17 11:19:58
2.1.1 stable Not tested Not yet tested Details 2019-09-17 07:22:08
2.1.0.4 stable Not tested Not yet tested Details 2019-06-11 14:19:19
2.1.0.3 stable Not tested Not yet tested Details 2019-06-11 13:38:50
2.1.0.2 stable Not tested Not yet tested Details 2019-06-05 13:36:06
2.1.0.1 stable Not tested Not yet tested Details 2019-06-01 17:21:40
2.1.0 stable Not tested Not yet tested Details 2019-05-26 10:36:24
2.0.4 stable Not tested Not yet tested Details 2019-04-22 14:56:14
2.0.3 stable Not tested Not yet tested Details 2019-04-18 15:26:56
2.0.2 stable Not tested Not yet tested Details 2019-04-09 12:02:45
2.0.1 stable Not tested Not yet tested Details 2019-03-22 08:50:42
2.0.0 stable Not tested Not yet tested Details 2019-03-21 23:52:56
1.0.1 stable Not tested Not yet tested Details 2018-06-27 12:07:03
1.0.0 stable Not tested Not yet tested Details 2017-11-10 09:37:22

Requires 29

Package Constraint
hryvinskyi/magento2-base ^2.1
hryvinskyi/magento2-configuration-fields ^1.0.11
hryvinskyi/magento2-theme-assets ^1.0
magento/framework *
magento/module-backend *
magento/module-catalog *
magento/module-catalog-search *
magento/module-checkout *
magento/module-config *
magento/module-contact *
magento/module-customer *
magento/module-graph-ql *
magento/module-layered-navigation *
magento/module-newsletter *
magento/module-paypal *
magento/module-quote *
magento/module-review *
magento/module-sales-rule *
magento/module-send-friend *
magento/module-store *
magento/module-url-rewrite *
magento/module-webapi *
magento/module-wishlist *
nyholm/psr7 ^1.5
php ~8.1.0||~8.2.0||~8.3.0||~8.4.0||~8.5.0
psr/http-client ^1.0
psr/http-factory ^1.0
psr/http-message ^1.0 || ^2.0
symfony/http-client ^5 || ^6 || ^7 || ^8

Requires-dev 1

Package Constraint
roave/security-advisories dev-latest

No QA results yet

QA pipelines haven't run for this version. Compatibility and quality results appear here once the vendor publishes a tagged release that gets ingested.

License
MIT
Authors

More from hryvinskyi

View vendor
Make it pay

Turn an existing module into recurring revenue.

If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.