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+.
Package info
github.com/DeployEcommerce/module-contact-graphql-recaptcha
Type:magento2-module
pkg:composer/deployecommerce/module-contact-graphql-recaptcha
Requires
- php: >=8.1
- magento/module-contact-graph-ql: *
- magento/module-re-captcha-contact: *
- magento/module-re-captcha-ui: *
- magento/module-re-captcha-validation-api: *
- magento/module-re-captcha-webapi-api: *
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:compileandcache:flushare not optional. The composite provider'sprovidersargument 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
/graphqland/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.