deployecommerce/module-contact-graphql-recaptcha

Backports the Magento 2.4.9 fix that applies reCAPTCHA to the ContactUs GraphQL mutation (security-package 1.1.8, commit LYNX-941). Remove after upgrading to 2.4.9+.

Maintainers

Package info

github.com/DeployEcommerce/module-contact-graphql-recaptcha

Type:magento2-module

pkg:composer/deployecommerce/module-contact-graphql-recaptcha

Transparency log

Statistics

Installs: 20

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.0.1 2026-09-01 14:10 UTC

This package is auto-updated.

Last update: 2026-09-01 14:12:55 UTC


README

Applies reCAPTCHA to the contactUs GraphQL mutation on Magento 2.4.8 and below.

This is a backport of the fix Adobe shipped in Magento 2.4.9 (magento/security-package 1.1.8, commit LYNX-941: Implement ReCaptcha for missing GraphQl mutations), packaged as a standalone module so it can be installed without upgrading the platform.

The problem

Magento's Magento_ReCaptchaContact gates the contact form controller only, via the controller_action_predispatch_contact_index_post observer.

The contactUs GraphQL mutation reaches the same Magento\Contact\Model\Mail sender with:

  • no reCAPTCHA validation,
  • no form key,
  • no CSRF token.

So a store with reCAPTCHA correctly enabled on its contact form is still open to automated contact-form email floods through /graphql. Because nginx does not log request bodies, the abuse is also close to invisible in access logs — the form POSTs show up and are rejected, while the mutation traffic that actually sends the mail does not.

A minimal proof of concept:

mutation {
  contactUs(input: {
    comment: "test"
    email: "test@example.com"
    name: "Test"
  }) {
    status
  }
}

On an unpatched 2.4.8 store this returns {"status": true} and sends the email, regardless of the reCAPTCHA configuration.

The fix

The module registers a WebapiValidationConfigProviderInterface implementation with Magento\ReCaptchaWebapiApi\Model\CompositeWebapiValidationConfigProvider. When the resolved endpoint is Magento\ContactGraphQl\Model\Resolver\ContactUs::resolve, it returns the existing contact validation config.

Two consequences worth noting:

  • The mutation is gated by the same reCAPTCHA settings already configured for the contact form — there is no new admin configuration, and no separate captcha id.
  • Enforcement stays config-gated. If reCAPTCHA is disabled for the contact form, nothing changes. Legitimate, token-bearing submissions are unaffected; only tokenless calls are blocked.

The code lives under the DeployEcommerce vendor namespace rather than overriding Magento\ReCaptchaContact, so it will not collide with core when the platform is upgraded.

Requirements

  • Magento 2.4.x below 2.4.9 (on 2.4.9+ core already provides this — see Removal)
  • PHP 8.1+
  • Magento_ReCaptchaContact, Magento_ReCaptchaUi, Magento_ReCaptchaValidationApi, Magento_ReCaptchaWebapiApi, Magento_ContactGraphQl

Installation

Via Composer

composer require deployecommerce/module-contact-graphql-recaptcha
bin/magento module:enable DeployEcommerce_ContactGraphQlReCaptcha
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

Via app/code

Copy the module to app/code/DeployEcommerce/ContactGraphQlReCaptcha, then run the same module:enable / setup:upgrade / setup:di:compile / cache:flush sequence.

setup:di:compile and cache:flush are not optional. The composite provider's providers argument is merged at DI-compile time and cached. A stale DI cache leaves the mutation silently ungated with the module apparently installed and enabled.

Configuration

None. The module reuses the existing contact-form reCAPTCHA configuration at Stores → Configuration → Security → Google reCAPTCHA Storefront → Contact.

If reCAPTCHA is not enabled for the contact form, enable it there first — otherwise this module has nothing to enforce.

Verifying

After deploying, re-run the mutation above with no reCAPTCHA token. It should return a reCAPTCHA validation error instead of {"status": true}, and send no email.

To check the wiring directly rather than over HTTP, resolve the composite provider and confirm it now returns a config for the ContactUs resolver where it previously returned null:

$endpoint = $objectManager->create(
    \Magento\ReCaptchaWebapiApi\Api\Data\EndpointInterface::class,
    [
        'serviceClass'  => \Magento\ContactGraphQl\Model\Resolver\ContactUs::class,
        'serviceMethod' => 'resolve',
    ]
);

$config = $objectManager
    ->get(\Magento\ReCaptchaWebapiApi\Model\CompositeWebapiValidationConfigProvider::class)
    ->getConfigFor($endpoint);

var_dump($config !== null); // true once the module is active

Endpoints this module does not target (for example CreateCustomer) should be unchanged.

Removal

Remove this module after upgrading to Magento 2.4.9 or later, where the equivalent provider ships in core as Magento\ReCaptchaContact\Model\WebapiConfigProvider. Leaving both installed registers two providers returning the same contact config for the same endpoint — harmless, but redundant.

bin/magento module:disable DeployEcommerce_ContactGraphQlReCaptcha
composer remove deployecommerce/module-contact-graphql-recaptcha
bin/magento setup:upgrade && bin/magento setup:di:compile && bin/magento cache:flush

Related hardening

Out of scope for this module, but worth pairing with it:

  • Rate-limit /graphql and /contacts/index/post/ at the web-server or WAF layer.
  • Audit for other unguarded GraphQL mutations that reach mail or account-creation paths.

Licence

MIT. See LICENSE.